Я давно положил глаз на Вязь. Я помню, как читал об этом, когда это была версия ‹ 0.10. Концепции, которые он предлагал, были фантастическими и действительно нашли отклик у меня в то время, потому что я также только что погрузился в Erlang за несколько месяцев до этого. Вы, наверное, могли бы сказать, что из-за того, что мне нравился Erlang, мне было легче полюбить Elm, даже когда он был очень молодым, потому что я стал поклонником функциональных языков и неизменяемых данных.
Однако Вяз может быть очень требовательным. И самым большим недостатком, на мой взгляд, являются ужасные сообщения компилятора. Хотя компилятор очень умен и хорошо написан, сообщения об ошибках обычно «в лучшем случае расплывчаты» для новых разработчиков Elm. И концепции — хотя они мне нравятся — может быть очень трудно реализовать реалистично, даже если вы думаете, что понимаете их теоретически.
Итак, вот мой «реальный» опыт разработки для Elm. И хотя я некоторое время наблюдал за ним, это первый раз, когда я вникал в него и программировал с его помощью. Так что я подхожу к разработке Elm практически с нуля.
Начать
Это никого не должно удивлять, но начинать лучше с простого. Лично я решил поучаствовать в маленьком побочном проекте с участием редактора персонажей для игры Mutants & Masterminds, которую я собираюсь запустить в ближайшее время. Но я не стремлюсь к предельной простоте, когда делаю такие вещи. Мне нравится добавлять кривую, чтобы действительно узнать язык, который я должен учить.
Игра включает в себя отслеживание множества характеристик, как это обычно бывает в подобных играх, поэтому я решил реализовать веб-страницу, на которой перечислены все возможные Атрибуты персонажа и которые можно изменить. Без валидации и прочего. Просто выведите список атрибутов, имен и описаний с полем ввода для изменения ранга (т.е. значения). Но вот в чем фишка: список атрибутов нужно было получить из API.
Я должен поблагодарить этот замечательный пост об использовании Elm для взаимодействия с API. Без него я уверен, что весь этот процесс обучения занял бы значительно больше времени, чем он.
Вяз и порты
Elm компилируется в JavaScript, но если вы хотите взаимодействия между кодом Elm и обычным кодом JavaScript, вам нужно использовать порты. Вы можете отправлять сообщения (вызывать код) или подписываться на сообщения (обратный вызов при возврате результата). Функция подписки особенно полезна, потому что Elm может создавать таймеры для запуска задач в более поздние моменты времени, поэтому можно получать выходные данные этих задач в другом коде JavaScript в зависимости от событий.
Все это относительно просто, и здесь была моя проблема: я знал, что «основная» функция модуля будет выполняться, когда он будет встроен в веб-страницу, но функция порта, описанная в сообщении в блоге, которое я прочитал также был немедленно казнен. Моей первой мыслью было, что, возможно, все порты запускаются один раз в начале выполнения кода, что кажется смешным, но, возможно, так все и работает. Поэтому я переименовал функцию порта и добавил секцию, похожую на функцию порта, которая делала что-то немного другое. Ни один не казнен. Хм.
После некоторых исследований я обнаружил неявное правило: если функция порта с именем runner существует, она будет выполнена сразу после загрузки кода. Ни в сообщении в блоге, которое я читал, ни в какой-либо официальной документации Elm это правило явно не указывалось, поэтому я излагаю его здесь и сейчас. Если у вас есть задача, которую вы хотите запустить сразу после того, как ваш модуль будет встроен в веб-страницу, создайте функцию исполнитель портов. А если у вас есть несколько задач, которые вы хотите запустить, используйте Модуль задач для таких функций, как карта, и затем и последовательность, чтобы запускать более одной задачи.
Декодирование JSON
Декодирование данных JSON из API — это не то, что вы бы назвали простым. Что действительно странно, поскольку предполагается, что Elm компилируется в JavaScript, поэтому можно подумать, что работа со строками JSON (преобразование строк JSON ‹-› структур Elm) должна быть простым процессом. Это не так.
Первое, что нужно знать: декодирование в запись — вероятно, самый простой способ взаимодействия с данными JSON. Создайте тип и используйте функции Json.Decode.object*, чтобы преобразовать четко определенный большой двоичный объект JSON в четко определенную структуру Elm.
Второе, что нужно знать: важен порядок. Если вы определяете свойства «a», «b» и «c» записи — в таком порядке — тогда вы должны декодировать JSON в этом порядке при использовании Json.Decode.object*. Примечание. Это не означает, что фактический большой двоичный объект JSON должен иметь какой-то определенный порядок своих собственных данных, а только то, что данные существуют. Даже при использовании такой функции, как («id» := Decode.int), вы не говорите «проанализируйте 'id' и поместите его в свойство 'id'», вы просто говорите «дайте мне значение 'id ' как целое число», и он вставит его в запись в соответствии с порядком, в котором он выполняется при вызове вашей функции.
Третье, что нужно знать: используйте «oneOf» и «succeed» для значений по умолчанию. Например, у меня было свойство, которое использовалось только на клиенте и не сохранялось на сервере, поэтому оно не передавалось через API прямо сейчас, но может быть в будущем. Чтобы создать «значение анализа или дать мне значение по умолчанию», вы используете такую функцию, как (oneOf [«свойство»: = тип, успех по умолчанию]). Это общая форма, и она гарантирует, что вы получите значение, даже если свойство, которое вы хотите проанализировать, не существует в большом двоичном объекте JSON. Просто убедитесь, что типы «тип» и «по умолчанию» совпадают, иначе вы можете получить ошибки.
Изменение данных в списке
Еще одна проблема, с которой я столкнулся, показалась мне простой. У вас есть один фрагмент данных в списке, который вы хотите отредактировать; как это сделать в Элме? К счастью, я добился большого прогресса в функциональном программировании с помощью Erlang, поэтому у меня была хорошая идея решить эту проблему, но я решил, что все равно поставлю ее здесь для почетного упоминания.
Поскольку Elm использует неизменяемые данные, вы не можете просто сказать «сохраните тот же список и просто замените в нем что-то одно». Вы также не можете изменить элемент в списке и просто вернуть список обратно. Если вы хотите удалить элемент, вы просто перебираете список, применяя фильтр к каждому элементу и добавляя только успешные элементы в новый список; результатом будет список без элементов, которые вам не нужны. Таким образом, чтобы изменить один или несколько элементов в списке, нам нужно использовать тот же подход: создать новый список.
Мы можем очень просто использовать List.map для выполнения основной работы, предоставляя только функцию, которая работает с каждым элементом в списке. И если мы хотим изменить только подмножество элементов, мы применяем фильтр к каждому элементу. Если элемент проходит, измените его. Если не пройдет, вернуть без изменений. Это будет выглядеть примерно так:
Используя каррирование функций, мы можем создавать гораздо более изобретательные способы изменения данных в списке, которые работают с другими основными конструкциями и концепциями языка Elm.
На первый взгляд кажется, что предстоит еще много работы, но на самом деле это открывает основу для простого создания высокомодульного кода, который может работать практически с любыми данными, которые вы в него вставляете. И если вас интересует каррирование функций…
Каррирование функций
Одна из вещей, которые мне нравятся в Elm, — это функция каррирования. Вы можете прочитать полное изложение по предоставленной ссылке, но короткая версия такова: если у вас есть функция, вы можете создать новую функцию, которая применила подмножество параметров и приняла остальные параметры. Это может быть трудно осмыслить, поэтому давайте рассмотрим пример.
Представьте, что у вас есть функция со следующим определением типа:
У нас есть функция, которая проверяет, содержится ли какая-то строка в списке строк. Стандартно, просто, без проблем. Но что, если бы у нас был список строк, которые мы хотели бы пропустить через эту функцию? Или что, если целевая строка поступает через пользовательский ввод и может не пройти проверку? Мы можем использовать наши круглые скобки для каррирования функции таким образом, чтобы ее можно было повторно использовать в различных ситуациях.
Используя его для проверки списка строк:
Использование его для проверки ввода, завернутого в результат:
Мы передали подмножество аргументов функции «содержит» (в данном случае список строк для проверки), чтобы создать каррированную функцию, которая принимает оставшиеся аргументы (в данном случае строку для проверки). Каррированная функция будет иметь определение типа:
Вы, наверное, уже думаете о полезных способах, но позвольте мне рассказать о моменте, когда это действительно щелкнуло для меня.
Я принимал два возможных пользовательских ввода, которые должны были быть целыми числами. В случае, если какой-либо вход не был целым числом, я бы проигнорировал его и вернул в последнее известное хорошее состояние. Если бы они оба были действительными целыми числами, я бы выполнил над ними операцию, которая включала бы список данных, которые у меня были. Вот что я сделал:
Это очень кратко и легко читается. Функция Result.map2 принимает функцию, принимающую два аргумента и два результата; если любой из результатов равен Err, он возвращает ошибку, в противном случае он применяет значения к функции и возвращает результат Ok. Но это возможно только благодаря особой функции, которую мы создали. Если бы у нас не было каррирования функций, а вместо этого нужно было бы немедленно применить все необходимые параметры, нам пришлось бы следовать совсем другому пути кода:
Несмотря на то, что ее не так уж сложно читать, ее определенно не так просто вывести (или поддерживать!), как предыдущую форму с использованием каррирования функций.
Общие мысли
В целом, мне очень нравится Elm, и я буду продолжать использовать его в своих личных проектах, где это имеет смысл. Прямо сейчас я создаю свой собственный движок для ведения блога, чтобы я мог поиграть с некоторыми новыми технологиями и узнать кое-что, а также создать собственный блог для личного пользования. Я планирую использовать Elm для административного интерфейса.
Публичный просмотр сообщений и тому подобное будет статическими файлами, генерируемыми по запросу серверной частью по мере необходимости. После того, как это будет сделано, я создам API для возврата JSON-дампов пользовательских и пост-данных. Пользователи с правами администратора смогут входить на веб-страницу Elm, которая будет использоваться для создания, редактирования и удаления сообщений и управления пользователями. Только основы для начала.
Я искренне рекомендую людям попробовать Elm. Хотя поначалу это может вызывать разочарование, работа со строго типизированным потоком компилятора и его понимание действительно помогают разработчику создавать более качественный интерфейсный код. Вам нужно заранее подумать о том, какие входные данные вы ожидаете и какие результаты вы получаете и возвращаете. Виртуальный DOM работает очень быстро. И то, как он обрабатывает события, поначалу кажется странным, но чем больше вы это делаете, тем более интуитивно понятным и приятным в работе становится; намного лучше, чем обработка событий в традиционном JavaScript.