Это Урок 27 из серии Переход с JavaScript на PureScript. Обязательно прочитайте введение в серию, где мы расскажем о целях и общих чертах, а также об установке, компиляции и запуске PureScript. Я буду публиковать новый учебник примерно раз в месяц. Так что заходите почаще, впереди еще много интересного!

Индекс | ‹‹ Введение ‹ Урок 26

В последнем уроке мы завершили изучение всех основных концепций функционального программирования. Теперь мы воспользуемся полученными знаниями для разработки приложения, использующего Spotify API для поиска точек соприкосновения между двумя музыкальными исполнителями. В этом первом из наших последних трех руководств мы определим необходимые конечные точки HTTP из API Spotify. Как только у нас будут конечные точки, мы набросаем высокоуровневую диаграмму потока данных. Затем мы начнем кодировать этот поток данных в PureScript.

Я позаимствовал план этой серии и образцы кода JavaScript с разрешения из курса egghead.io Профессор Фрисби представляет компонуемый функциональный JavaScript Брайана Лонсдорфа — спасибо, Брайан! Фундаментальное предположение состоит в том, что вы смотрели его видео по теме, прежде чем заняться эквивалентной абстракцией PureScript. Брайан исключительно хорошо раскрывает представленные концепции, и я считаю, что лучше, если вы разберетесь с их реализацией, не выходя из JavaScript.

Важное примечание. API Spotify эволюционировал с тех пор, как Брайан опубликовал свое руководство. В частности, они удалили неаутентифицированные вызовы ко всем своим конечным точкам API. Следовательно, чтобы это приложение работало, вам потребуется учетная запись Spotify для получения токена доступа к API.

Вы найдете текст и примеры кода для этого руководства на Github. Если вы прочитали что-то, что, по вашему мнению, можно было бы объяснить лучше, или пример кода, который нуждается в рефакторинге, сообщите мне об этом в комментарии или отправьте мне запрос на включение. Кроме того, если вам нравится то, что вы видите, пожалуйста, поставьте звездочку, чтобы помочь мне опубликовать эти уроки.

Что мы строим

Мы собираемся создать приложение, которое находит общих музыкальных исполнителей, совместно используемых двумя или более исполнителями, путем запроса Spotify API. Например, если пользователь вводит Битлз и Роллинг Стоунз, наше приложение возвращает массив исполнителей, которые, по мнению алгоритма Spotify, являются общими. Код готового приложения находится в Уроке 29. Вы можете запустить эту окончательную реализацию, набрав в своем терминале следующее:

# Compiles and produces a single executable 'index.js' in root dir
$ npm run bundle 
# Use node to run it 
$ node index.js Beatles 'Rolling Stones' 
# Returns 34 related artists; 5 artists in common. 
$ (Tuple 34 ["Jimi Hendrix","The Kinks","George Harrison","Chuck Berry","Eric Clapton","John Lennon"])

Дорожная карта развития

Вот наш план развития:

  1. Начните с просмотра Документации по веб-API Spotify, чтобы найти подходящие конечные точки.
  2. Протестируйте эти конечные точки в терминале, используя Curl и Консоль Spotify, чтобы убедиться, что они доставляют нужные нам метаданные.
  3. Создайте диаграмму потока данных нашего приложения.
  4. Завершите программирование захвата пользовательских аргументов, одновременно смоделировав остальную часть потока данных в PureScript.

Мы подробно рассмотрим каждый шаг нашего плана в следующих четырех разделах.

Шаг 1. Найдите документы API Spotify.

После краткого ознакомления с документацией по API Spotify оказалось, что на странице Исполнители (см. рис. 1) есть метаданные, необходимые для нашего приложения. В частности, есть конечная точка /v1/artists/{id}/related-artists, которая, учитывая id исполнителя, возвращает связанных с ним исполнителей. Как получить это id? Что ж, есть еще страница Поиск предмета, показанная на рис. 1, намекающая на то, что мы можем получить исполнителя id, введя запрос вместе с нужным нам типом метаданных.

Например, если мы хотим найти The Beatles, то конечная точка нашего API — v1/search?q=Beatles&type=artist&limit=1. Чтобы определить, достаточно ли этих конечных точек, давайте проверим их с помощью инструментов, описанных в следующем разделе.

Шаг 2 — Проверка ответов конечной точки

Вооружившись нашими предполагаемыми конечными точками API, мы готовы начать их тестирование в консоли Spotify, показанной выше. Я нашел этот подход самым простым, но, естественно, вы также можете попробовать их в своем терминале, используя Curl. Например, если мы хотим получить исполнителя id для The Beatles, то запрос Curl выглядит следующим образом:

curl -X "GET" "https://api.spotify.com/v1/search?q=Beatles&type=artist&limit=1" -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: Bearer YourSecretTokenGoesHere"

Обратите внимание, что ответ JSON содержит исполнителя id в массиве items, вложенном в artists:

{ 
  "artists": { 
    "href": "https://api.spotify.com/v1/search?query=Beatles&type=artist&offset=0&limit=1", 
    "items": [ 
      { 
        ... 
        "genres": [ "british invasion", 
                    "classic rock", 
                    "merseybeat", 
                    "psychedelic rock", 
                    "rock"
                  ], 
        "href": "https://api.spotify.com/v1/artists/3WrFJ7ztbogyGnTHbHJFl2", 
        "id": "3WrFJ7ztbogyGnTHbHJFl2", 
        ... 
      ... 
    }] 
... 
}

Удовлетворившись тем, что эти две конечные точки предоставляют нужные нам метаданные, мы можем перейти к наброску нашего потока данных, прежде чем начинать кодировать на PureScript.

Шаг 3 — Нарисуйте схему потока данных

В последнее время я заметил повышенный интерес сообщества JavaScript к использованию конечных состояний для декларативного моделирования логики в своих программах. Это отличный способ выявить крайние случаи и устранить ошибки, возникающие из-за неправильной логики. Например, на момент написания этой статьи работа Дэвида Хуршида над XState набрала более 11,5 тысяч звезд на Github. Будь то диаграммы состояний, деревья поведения или диаграммы потоков данных; все они полезны для описания поведения вашего кода как программистам, так и непрограммистам.

Диаграмма потока данных явно иллюстрирует поток данных, с объектами, представляющими преобразования, и потоком данных, изображенным в виде линии или конвейера. Их также просто рисовать. Вы можете моделировать поток данных всего четырьмя основными символами, представляющими процесс, хранилище данных, внешний объект и примечание. Например, на приведенной ниже диаграмме потока данных используются все четыре основных символа.

Крайний левый блок External Entity представляет пользователя или другую внешнюю систему, которая предоставляет данные или получает выходные данные от системы. Далее идет примечание, стрелка которого указывает направление пути данных, а метка описывает сами данные. Блок Process получает эти входные данные и преобразовывает их перед передачей их окончательному внешнему объекту. В этом случае мы используем объект хранилища данных для хранения данных, которые будут использоваться в другое время. Обратите внимание, что мы называем терминаторами внешних сущностей, потому что они обозначают, где данные берут начало и где они заканчиваются.

Существует только одно основное правило при рисовании диаграмм потоков данных — весь поток должен начинаться и заканчиваться блоком Process. Дело здесь в том, что внешний объект не должен предоставлять данные другому внешнему объекту без предварительной обработки. Вместо этого поместите блок Process между двумя внешними объектами.

Для получения дополнительной информации о диаграммах потоков данных посетите веб-сайт Visual Paradigm. Вы можете использовать их инструменты или выбрать свою любимую программу для рисования, чтобы добиться аналогичных результатов. Я использовал Sketch.app, чтобы нарисовать фигуры, которые вы видите в этом разделе. Если вы пользователь Sketch, то смело берите мою страницу с символами из файла flow_diagram.sketch,, расположенного в моем репозитории GitHub.

Блок-схема данных приложения

Наше приложение простое и, следовательно, схема потока данных, показанная на рисунке 3, тоже простая. В следующем руководстве мы добавим еще один слой на диаграмму, чтобы лучше проиллюстрировать наши вызовы HTTP GET к API Spotify. Но на высоком уровне кажется, что мы рассматриваем примерно три основные функции: Процесс 1 для извлечения имен исполнителей из аргументов пользовательской командной строки; Процесс 2, чтобы взять имена этих исполнителей и получить их идентификаторы, и Процесс 3, чтобы найти соответствующих исполнителей, которых они разделяют.

Шаг 4 — Смоделируйте поток данных в PureScript

Теперь, когда у нас есть набросок потока данных, мы можем приступить к кодированию нашего приложения. В первой части этой серии, состоящей из трех частей, мы остановимся на получении аргументов имени исполнителя в процессе 1 и смоделируем два других процесса, которые включают HTTP-запросы Get.

Получение имен исполнителей из аргументов командной строки

Чтобы получить аргументы имени исполнителя из командной строки, мы используем process.argv (см. документы) непосредственно в Node.js, запуская процесс Node.js как

$ node index.js Beatles Oasis

Spago, наш менеджер пакетов PureScript, может упаковать проект PureScript в один JS-файл. Команда для этого:

$ spago bundle-module --main Main --to index.js

Я включил сценарий bundle в файл package.json, чтобы помочь вам вспомнить. Вызов npm run bundle в командной строке запускает Spago для создания единого исполняемого файла index.js с удаленным мертвым кодом, который мы можем запустить с помощью Node. В приведенном выше примере process.argv генерирует выходной массив:

0: /usr/local//bin/node 
1: ~/tut27/index.js 
2: Beatles 
3: Oasis

Это третий и четвертый аргументы, которые мы должны зафиксировать, чтобы выполнить процесс 1 на нашей диаграмме. Более того, мы не ограничиваемся только двумя художниками. Таким образом, этот процесс должен быть способен захватывать два или более имен исполнителей и помечать ошибку, когда имен исполнителей нет.

Включение argv в задачу

Как Брайан упоминает в своем видео, лучше всего обернуть argv в асинхронную задачу, чей обратный вызов доставляет имена наших исполнителей и обрабатывает любые ошибки. Мы использовали Task в нескольких уроках, начиная с Урока 13. Итак, начните с этого, если вы не знакомы с этой абстракцией.

Основные строки кода, которые выполняют процесс 1:

import Node.Process (argv) 
type Error = String
type Names = Array String 
names :: TaskE Error Names 
names =
  let 
    checkArgs :: Effect (Either Error Names)
    checkArgs = do
       args <- (drop 2) <$> argv
       pure $ 
         if (length args > 0)
           then Right args 
           else Left "you must enter at least one artist"
  in newTask $ \callback -> do 
    checkArgs >>= \args ->
      callback $ either (\e -> rej e) (\xs -> res xs) args
    pure $ nonCanceler 
main :: Effect Unit
  void $ launchAff $ 
    names # 
    fork (\e -> Console.error $ "Error: " <> e) 
         (\xs -> Console.logShow xs)

Начиная с нашей функции main, launchAff запускает наш метод names асинхронно, а fork обрабатывает один из двух возможных результатов — успех или неудачу. Здесь «успех» означает, что обратный вызов возвращает массив с именами исполнителей, которые мы получили из командной строки. И «сбой» приводит к сообщению об ошибке, зарегистрированному на терминале.

В методе names мы встраиваем функцию checkArgs, которая берет имена артистов из argv (чей тип Effect (Array String)). Из Урока 14 мы знаем, что argv — это массив и, следовательно, это функтор, который мы можем отображать. Мы map от argv до drop первых двух элементов в массиве.

Должно быть по крайней мере одно имя исполнителя, связанное с args, и мы проверяем это, убедившись, что длина args больше 0. Если это так, то мы поместим наши имена исполнителей в конструктор Right из Either (см. Урок 3) монада. Если нет, то оберните сообщение об ошибке в конструкторе Left, потому что по соглашению это означает сбой. Наконец, мы заключаем результат в Task и очищаем перед возвратом в main.

Насмешливые процессы 2 и 3

Возвращаясь к нашей диаграмме потока данных, процесс 2 берет имена исполнителей, ищет и возвращает идентификаторы исполнителей. Для этого процесса требуется HTTP-вызов GET к нашей конечной точке поиска Spotify для извлечения идентификаторов артистов из ответа JSON. Мы рассмотрим все это в следующем уроке. Тем временем мы смоделируем весь поток данных, заключив имя каждого исполнителя в Task, а их поддельный идентификатор — в другой Task. Обратите внимание, что taskOf из Control.Monad.Task всегда возвращает успешное Task. Однако вы можете смоделировать отказ, используя taskRejected из того же модуля.

type Id = Int
type Artist = String
-- point free, assume artist name is valid for now
findArtist :: Artist -> TaskE Error Artist 
findArtist = taskOf
-- return an arbitrary id
artistId :: Artist -> TaskE Error Id 
artistId _ = taskOf 10

Процесс 3 находит артистов, которые связаны между собой. Здесь мы снова отложим фактическую работу по вызову API Spotify для следующего учебника. А пока давайте просто обернем массив произвольных исполнителей в файл Task.

type RelatedArtists = Array String
relatedArtists :: Id -> TaskE Error RelatedArtists
relatedArtists id = taskOf ["John Lennon", "Paul McCartney"]

Наконец, внутри функции related, мы объединяем весь поток данных, используя пару монадических привязок, которые пронизывают наши данные через наши три функции findartist, artistId и relatedArtists; точно так же, как мы смоделировали это на диаграмме потока данных.

type RelatedArtists = Array String
related :: String -> TaskE Error RelatedArtists
related name = relatedArtists =<< artistId =<< findArtist name

Теперь, предполагая, что нужно обработать более одного имени исполнителя, нам нужна функция, которая проходит по нашему массиву имен исполнителей и возвращает всех связанных с ними исполнителей. Ключевое слово здесь — traverse из модуля PureScript Data.Traversable, который запускает наш метод related по имени каждого исполнителя и собирает результаты во вложенный массив связанных исполнителей. Итак, мы изменим нашу функцию main на:

main :: Effect Unit 
main = do 
  void $ launchAff $ 
    names >>= traverse related # 
    fork (\e -> Console.error $ "Error: " <> e) 
         (\xs -> Console.logShow xs)

Резюме

В этом уроке мы начали завершать эту серию, применяя то, что узнали о функциональном программировании. Мы использовали наши недавно приобретенные навыки FP для разработки узлового приложения, которое находит общие черты между двумя или более музыкальными исполнителями с помощью API Spotify. В первой части этой серии, состоящей из трех частей, мы выполнили большую часть подготовительной работы: 1) просмотрели документацию по API Spotify; 2) тестирование возможных ответов конечной точки; 3) набросок нашей схемы выполнения потока данных; и 4) имитация потока данных в PureScript. В нашем коде PureScript мы создали функции, которые успешно извлекают аргументы имени исполнителя из командной строки, имитируя оставшийся поток данных.

В следующем руководстве мы добавим еще один слой в нашу диаграмму потока данных, который дополнительно иллюстрирует данные, поступающие в API Spotify и получаемые из него. Мы также поработаем над заменой макетов рабочим кодом. Мы также попытаемся найти лучшую абстракцию для обработки асинхронных вызовов API. В качестве тизера позвольте мне сказать, что вас ждет сюрприз.

Наконец, если вам нравятся эти уроки, пожалуйста, помогите мне рассказать об этом другим, рекомендуя эту статью и отмечая ее в социальных сетях. До скорого.