Изучаем Elm с помощью барабанного секвенсора (часть 1)
Опубликовано: 2022-03-10Если вы являетесь фронтенд-разработчиком и следите за эволюцией одностраничных приложений (SPA), вероятно, вы слышали об Elm, функциональном языке, который вдохновил Redux. Если нет, то это язык компиляции в JavaScript, сравнимый с проектами SPA, такими как React, Angular и Vue.
Как и они, он управляет изменениями состояния через свой виртуальный дом, стремясь сделать код более удобным и производительным. Основное внимание уделяется удовлетворению разработчиков, высококачественным инструментам и простым повторяемым шаблонам. Некоторые из его ключевых отличий включают статическую типизацию, удивительно полезные сообщения об ошибках и то, что это функциональный язык (в отличие от объектно-ориентированного).
Мое знакомство состоялось из выступления Эвана Чаплицки, создателя Elm, о его видении интерфейса разработчика и, в свою очередь, видении Elm. Поскольку кто-то также сосредоточился на ремонтопригодности и удобстве использования клиентской разработки, его выступление действительно нашло во мне отклик. Я попробовал Elm в побочном проекте год назад и продолжаю получать удовольствие от его функций и проблем, чего не было с тех пор, как я впервые начал программировать; Я снова начинающий. Кроме того, я обнаружил, что могу применять многие практики Elm на других языках.
Развитие понимания зависимости
Зависимости есть везде. Сокращая их, вы можете повысить вероятность того, что ваш сайт будет использоваться наибольшим количеством людей в самых разных сценариях. Прочитайте статью по теме →
В этой статье, состоящей из двух частей, мы создадим пошаговый секвенсор для программирования ударных в Elm, демонстрируя при этом некоторые из лучших возможностей языка. Сегодня мы рассмотрим основные концепции Elm, то есть начало работы, использование типов, отрисовку представлений и обновление состояния. Во второй части этой статьи мы углубимся в более сложные темы, такие как простая обработка крупных рефакторингов, настройка повторяющихся событий и взаимодействие с JavaScript.
Поэкспериментируйте с окончательным проектом здесь и проверьте его код здесь.
Начало работы с вязом
Чтобы следовать этой статье, я рекомендую использовать Ellie, интерфейс разработчика Elm в браузере. Вам не нужно ничего устанавливать, чтобы запустить Ellie, и вы можете разрабатывать в ней полнофункциональные приложения. Если вы хотите установить Elm на свой компьютер, лучше всего выполнить настройку, следуя официальному руководству по началу работы.
В этой статье я буду ссылаться на незавершенные версии Ellie, хотя я разработал секвенсор локально. И хотя CSS можно полностью написать на Elm, я написал этот проект на PostCSS. Это требует небольшой настройки Elm Reactor для локальной разработки, чтобы стили загружались. Для краткости я не буду касаться стилей в этой статье, но ссылки Элли включают все минимизированные стили CSS.
Elm — это автономная экосистема, в которую входят:
- Вяз Сделать
Для компиляции кода Elm. Хотя Webpack по-прежнему популярен для производства проектов Elm наряду с другими активами, он не требуется. В этом проекте я решил исключить Webpack и положиться наelm make
для компиляции кода. - Вяз пакет
Менеджер пакетов, сравнимый с NPM, для использования пакетов/модулей, созданных сообществом. - Вяз Реактор
Для запуска автоматически компилируемого сервера разработки. Что еще более примечательно, он включает в себя отладчик Time Traveling Debugger, упрощающий просмотр состояний вашего приложения и воспроизведение ошибок. - Вяз Репл
Для написания или тестирования простых выражений Elm в терминале.
Все файлы Elm считаются modules
. Начальные строки любого файла будут включать module FileName exposing (functions)
, где FileName
— буквальное имя файла, а functions
— общедоступные функции, которые вы хотите сделать доступными для других модулей. Сразу после определения модуля идут импорты из внешних модулей. Далее идут остальные функции.
module Main exposing (main) import Html exposing (Html, text) main : Html msg main = text "Hello, World!"
Этот модуль с именем Main.elm
предоставляет единственную функцию main
и импортирует Html
и text
из модуля/пакета Html
. main
функция состоит из двух частей: аннотации типа и фактической функции. Аннотации типов можно рассматривать как определения функций. Они указывают типы аргументов и тип возвращаемого значения. В нашем случае наша функция main
не принимает аргументов и возвращает Html msg
. Сама функция отображает текстовый узел, содержащий «Hello, World». Чтобы передать аргументы функции, мы добавляем имена через пробел перед знаком равенства в функции. Мы также добавляем типы аргументов в аннотацию типа в порядке следования аргументов со стрелкой.
add2Numbers : Int -> Int -> Int add2Numbers first second = first + second
В JavaScript подобная функция сравнима:
function add2Numbers(first, second) { return first + second; }
А на типизированном языке, таком как TypeScript, это выглядит так:
function add2Numbers(first: number, second: number): number { return first + second; }
add2Numbers
принимает два целых числа и возвращает целое число. Последнее значение в аннотации всегда является возвращаемым значением, поскольку каждая функция должна возвращать значение. Мы вызываем add2Numbers
с 2 и 3, чтобы получить 5, как add2Numbers 2 3
.
Точно так же, как вы привязываете компоненты React, нам нужно привязать скомпилированный код Elm к DOM. Стандартный способ привязки — вызвать embed()
в нашем модуле и передать в него элемент DOM.
<script> const container = document.getElementById('app'); const app = Elm.Main.embed(container); <script>
Хотя наше приложение на самом деле ничего не делает, нам достаточно, чтобы скомпилировать наш код Elm и отобразить текст. Проверьте это на Элли и попробуйте изменить аргументы на add2Numbers
в строке 26.
Моделирование данных с помощью типов
Исходя из языка с динамической типизацией, такого как JavaScript или Ruby, типы могут показаться излишними. Эти языки определяют, какие функции типов берут из значения, передаваемого во время выполнения. Написание функций обычно считается более быстрым, но вы теряете безопасность, гарантируя, что ваши функции могут правильно взаимодействовать друг с другом.
Напротив, Elm имеет статическую типизацию. Он полагается на свой компилятор, чтобы убедиться, что значения, передаваемые функциям, совместимы до времени выполнения. Это означает отсутствие исключений во время выполнения для ваших пользователей, и именно так Elm может гарантировать отсутствие исключений во время выполнения. Там, где ошибки типов во многих компиляторах могут быть особенно загадочными, Elm фокусируется на том, чтобы их было легко понять и исправить.
Elm делает начало работы с типами очень удобным. На самом деле вывод типов в Elm настолько хорош, что вы можете не писать аннотаций, пока не освоитесь с ними. Если вы новичок в типах, я рекомендую полагаться на предложения компилятора, а не пытаться написать их самостоятельно.
Давайте приступим к моделированию наших данных с использованием типов. Наш пошаговый секвенсор представляет собой визуальную временную шкалу того, когда должен играть конкретный сэмпл ударных. Временная шкала состоит из треков , каждому из которых назначен определенный барабанный сэмпл и последовательность шагов . Шаг можно считать моментом времени или битом. Если шаг активен , сэмпл должен запускаться во время воспроизведения, а если шаг неактивен , семпл должен оставаться без звука. Во время воспроизведения секвенсор будет перемещаться по каждому шагу, воспроизводя сэмплы активных шагов. Скорость воспроизведения задается количеством ударов в минуту (BPM) .
Моделирование нашего приложения в JavaScript
Чтобы лучше понять наши типы, давайте рассмотрим, как смоделировать этот барабанный секвенсор в JavaScript. Есть набор треков. Каждый объект дорожки содержит информацию о себе: название дорожки, сэмпл/клип, который будет запускаться, и последовательность значений шага.
tracks: [ { name: "Kick", clip: "kick.mp3", sequence: [On, Off, Off, Off, On, etc...] }, { name: "Snare", clip: "snare.mp3", sequence: [Off, Off, Off, Off, On, etc...] }, etc... ]
Нам нужно управлять состоянием воспроизведения между воспроизведением и остановкой.
playback: "playing" || "stopped"
Во время воспроизведения нам нужно определить, какой шаг должен быть воспроизведен. Мы также должны учитывать производительность воспроизведения, а не обход каждой последовательности в каждой дорожке каждый раз, когда шаг увеличивается; мы должны свести все активные шаги в одну последовательность воспроизведения. Каждая коллекция в последовательности воспроизведения представляет все сэмплы, которые должны быть воспроизведены. Например, ["kick", "hat"]
означает, что должны воспроизводиться сэмплы бочки и хай-хэта, тогда как ["hat"]
означает, что должен воспроизводиться только хай-хэт. Нам также нужно, чтобы каждая коллекция ограничивала уникальность выборки, чтобы мы не получили что-то вроде ["hat", "hat", "hat"]
.
playbackPosition: 1 playbackSequence: [ ["kick", "hat"], [], ["hat"], [], ["snare", "hat"], [], ["hat"], [], ... ],
И нам нужно установить темп воспроизведения или BPM.
bpm: 120
Моделирование с помощью типов в Elm
Преобразование этих данных в типы Elm, по сути, описывает то, из чего мы ожидаем, что наши данные будут сделаны. Например, мы уже называем нашу модель данных моделью , поэтому мы называем ее так с помощью псевдонима типа. Псевдонимы типов используются для облегчения чтения кода. Они не являются примитивным типом , таким как логическое или целое число; это просто имена, которые мы даем примитивному типу или структуре данных. Используя один, мы определяем любые данные, которые следуют нашей структуре модели, как модель , а не как анонимную структуру. Во многих проектах Elm основная структура называется Model.
type alias Model = { tracks : Array Track , playback : Playback , playbackPosition : PlaybackPosition , bpm : Int , playbackSequence : Array (Set Clip) }
Хотя наша модель немного похожа на объект JavaScript, она описывает запись Elm. Записи используются для организации связанных данных в несколько полей, которые имеют собственные аннотации типов. К ним легко получить доступ с помощью field.attribute
и легко обновить, что мы увидим позже. Объекты и записи очень похожи, но имеют несколько ключевых отличий:
- Несуществующие поля не могут быть вызваны
- Поля никогда не будут
null
илиundefined
-
this
иself
не могут быть использованы
Наша коллекция дорожек может состоять из трех возможных типов: списки, массивы и наборы. Короче говоря, списки — это неиндексированные коллекции общего назначения, массивы — индексированные, а наборы содержат только уникальные значения. Нам нужен индекс, чтобы знать, какой шаг отслеживания был переключен, и, поскольку массивы индексируются, это наш лучший выбор. В качестве альтернативы мы могли бы добавить идентификатор к дорожке и фильтровать из списка.
В нашей модели мы набрали дорожки в массив track , другая запись: tracks : Array Track
. Трек содержит информацию о себе. И name, и clip являются строками, но мы набрали псевдоним clip, потому что знаем, что на него будут ссылаться в другом месте кода другие функции. Присвоив ему псевдоним, мы начинаем создавать самодокументирующийся код. Создание типов и псевдонимов типов позволяет разработчикам моделировать модель данных для бизнес-модели, создавая универсальный язык.
type alias Track = { name : String , clip : Clip , sequence : Array Step } type Step = On | Off type alias Clip = String
Мы знаем, что последовательность будет массивом значений включения/выключения. Мы могли бы задать его как массив логических значений, например sequence : Array Bool
, но мы бы упустили возможность выразить нашу бизнес-модель! Учитывая, что пошаговые секвенсоры состоят из шагов , мы определяем новый тип Step . Step может быть псевдонимом типа для boolean
, но мы можем пойти еще дальше: Steps имеет два возможных значения, on и off, так мы определяем тип union. Теперь шаги могут быть только включенными или выключенными, что делает все остальные состояния невозможными.
Мы определяем другой тип для Playback
, псевдоним для PlaybackPosition
, и используем Clip при определении playbackSequence
как массива, содержащего наборы клипов. BPM назначается как стандартный Int
.
type Playback = Playing | Stopped type alias PlaybackPosition = Int
Хотя при начале работы с типами возникает немного больше накладных расходов, наш код намного удобнее в сопровождении. Он самодокументируется и использует универсальный язык с нашей бизнес-моделью. Уверенность, которую мы получаем, зная, что наши будущие функции будут взаимодействовать с нашими данными так, как мы ожидаем, не требуя тестов, стоит времени, необходимого для написания аннотации. И мы могли бы положиться на вывод типа компилятора, чтобы предложить типы, поэтому написать их так же просто, как скопировать и вставить. Вот полное объявление типа.
Использование архитектуры Elm
Архитектура Elm — это простой шаблон управления состоянием, который естественным образом появился в языке. Он фокусируется на бизнес-модели и обладает высокой масштабируемостью. В отличие от других сред SPA, Elm самоуверен в своей архитектуре — именно так структурированы все приложения, что упрощает адаптацию. Архитектура состоит из трех частей:
- Модель , содержащая состояние приложения, и структура, которую мы набираем с псевдонимом модели
- Функция обновления , которая обновляет состояние
- И функция просмотра , которая визуализирует состояние
Давайте начнем создавать наш барабанный секвенсор, изучая архитектуру Elm на практике по ходу дела. Мы начнем с инициализации нашего приложения, рендеринга представления, а затем обновления состояния приложения. Имея опыт работы с Ruby, я предпочитаю более короткие файлы и разбиваю свои функции Elm на модули, хотя иметь большие файлы Elm вполне нормально. Я создал начальную точку на Элли, но локально я создал следующие файлы:
- Types.elm, содержащий все определения типов
- Main.elm, который инициализирует и запускает программу
- Update.elm, содержащий функцию обновления, которая управляет состоянием.
- View.elm, содержащий код Elm для преобразования в HTML.
Инициализация нашего приложения
Лучше начинать с малого, поэтому мы уменьшаем модель, чтобы сосредоточиться на построении одной дорожки, содержащей шаги, которые включаются и выключаются. Хотя мы уже думаем, что знаем всю структуру данных, начав с малого, мы можем сосредоточиться на рендеринге дорожек в виде HTML. Это снижает сложность, и код вам не понадобится. Позже компилятор проведет нас через рефакторинг нашей модели. В файле Types.elm мы сохраняем наши типы Step и Clip, но меняем модель и дорожку.
type alias Model = { track : Track } type alias Track = { name : String , sequence : Array Step } type Step = On | Off type alias Clip = String
Чтобы визуализировать Elm как HTML, мы используем пакет Elm Html. Он имеет опции для создания трех типов программ, которые строятся друг на друге:
- Программа для начинающих
Сокращенная программа, исключающая побочные эффекты и особенно полезная для изучения архитектуры Elm. - Программа
Стандартная программа, обрабатывающая побочные эффекты, полезная для работы с базами данных или инструментами, существующими вне Elm. - Программа с флагами
Расширенная программа, которая может инициализировать себя реальными данными вместо данных по умолчанию.
Хорошей практикой является использование самого простого типа программы, потому что позже его легко изменить с помощью компилятора. Это обычная практика при программировании в Elm; используйте только то, что вам нужно, и измените это позже. Для наших целей мы знаем, что нам нужно иметь дело с JavaScript, который считается побочным эффектом, поэтому мы создаем Html.program
. В Main.elm нам нужно инициализировать программу, передав функции в ее поля.
main : Program Never Model Msg main = Html.program { init = init , view = view , update = update , subscriptions = always Sub.none }
Каждое поле в программе передает функцию среде выполнения Elm, которая управляет нашим приложением. В двух словах, среда выполнения Elm:
- Запускает программу с нашими начальными значениями из
init
. - Отрисовывает первое представление, передавая нашу инициализированную модель в
view
. - Постоянно перерисовывает представление, когда сообщения передаются для
update
из представлений, команд или подписок.
Локально наши функции view
и update
будут импортированы из View.elm
и Update.elm
соответственно, и мы создадим их через мгновение. subscriptions
прослушивают сообщения, чтобы вызывать обновления, но пока мы игнорируем их, назначая always Sub.none
. Наша первая функция, init
, инициализирует модель. Думайте об init
как о значениях по умолчанию для первой загрузки. Мы определяем его с помощью одной дорожки с именем «kick» и последовательностью шагов Off. Поскольку мы не получаем асинхронные данные, мы явно игнорируем команды с Cmd.none
для инициализации без побочных эффектов.
init : ( Model, Cmd.Cmd Msg ) init = ( { track = { sequence = Array.initialize 16 (always Off) , name = "Kick" } } , Cmd.none )
Наша аннотация типа инициализации соответствует нашей программе. Это структура данных, называемая кортежем, которая содержит фиксированное количество значений. В нашем случае Model
и команды. На данный момент мы всегда игнорируем команды, используя Cmd.none
, пока не будем готовы обрабатывать побочные эффекты позже. Наше приложение ничего не отображает, но оно компилируется!
Рендеринг нашего приложения
Давайте построим наши представления. На данный момент наша модель имеет одну дорожку, так что это единственное, что нам нужно отрендерить. Структура HTML должна выглядеть так:
<div class="track"> <p class "track-title">Kick</p> <div class="track-sequence"> <button class="step _active"></button> <button class="step"></button> <button class="step"></button> <button class="step"></button> etc... </div> </div>
Мы создадим три функции для отображения наших представлений:
- Один для рендеринга одной дорожки, которая содержит название дорожки и последовательность.
- Другой для рендеринга самой последовательности
- И еще один для рендеринга каждой отдельной кнопки шага в последовательности
Наша первая функция просмотра будет отображать одну дорожку. Мы полагаемся на аннотацию нашего типа renderTrack : Track -> Html Msg
, чтобы обеспечить прохождение одной дорожки. Использование типов означает, что мы всегда знаем, что renderTrack
будет дорожка. Нам не нужно проверять, существует ли поле name
в записи или передана ли строка вместо записи. Elm не будет компилироваться, если мы попытаемся передать в renderTrack
что-либо, кроме Track
. Еще лучше, если мы совершим ошибку и случайно попытаемся передать функции что-либо, кроме дорожки, компилятор выдаст нам дружественные сообщения, чтобы указать нам правильное направление.
renderTrack : Track -> Html Msg renderTrack track = div [ class "track" ] [ p [ class "track-title" ] [ text track.name ] , div [ class "track-sequence" ] (renderSequence track.sequence) ]
Это может показаться очевидным, но все Elm есть Elm, включая написание HTML. Не существует языка шаблонов или абстракции для написания HTML — это все Elm. Элементы HTML — это функции Elm, которые принимают имя, список атрибутов и список дочерних элементов. Итак div [ class "track" ] []
выводит <div class="track"></div>
. Списки в Elm разделяются запятыми, поэтому добавление идентификатора в div будет выглядеть как div [ class "track", id "my-id" ] []
.
Последовательность дорожки, обертывающая div, передает track-sequence
дорожки нашей второй функции, renderSequence
. Он принимает последовательность и возвращает список кнопок HTML. Мы могли бы оставить renderSequence
в renderTrack
, чтобы пропустить дополнительную функцию, но я считаю, что разбивать функции на более мелкие части гораздо проще. Кроме того, мы получаем еще одну возможность определить более строгую аннотацию типа.
renderSequence : Array Step -> List (Html Msg) renderSequence sequence = Array.indexedMap renderStep sequence |> Array.toList
Мы отображаем каждый шаг в последовательности и передаем его в функцию renderStep
. В JavaScript сопоставление с индексом будет выглядеть так:
sequence.map((node, index) => renderStep(index, node))
По сравнению с JavaScript сопоставление в Elm происходит почти наоборот. Мы вызываем Array.indexedMap
, который принимает два аргумента: функцию, которая будет применена к карте ( renderStep
), и массив для отображения ( sequence
). renderStep
— наша последняя функция, и она определяет, активна кнопка или нет. Мы используем indexedMap
, потому что нам нужно передать индекс шага (который мы используем в качестве идентификатора) самому шагу, чтобы передать его функции обновления.
renderStep : Int -> Step -> Html Msg renderStep index step = let classes = if step == On then "step _active" else "step" in button [ class classes ] []
renderStep
принимает индекс в качестве первого аргумента, шаг в качестве второго и возвращает обработанный HTML. Используя блок let...in
для определения локальных функций, мы назначаем класс _active
для On Steps и вызываем функцию наших классов в списке атрибутов кнопки.
Обновление состояния приложения
На данный момент наше приложение отображает 16 шагов в последовательности ударов, но щелчок не активирует шаг. Чтобы обновить состояние шага, нам нужно передать сообщение ( Msg
) в функцию обновления. Мы делаем это, определяя сообщение и прикрепляя его к обработчику событий для нашей кнопки.
В Types.elm нам нужно определить наше первое сообщение, ToggleStep
. Для индекса последовательности потребуется Int
и Step
. Затем в renderStep
мы прикрепляем сообщение ToggleStep
к событию нажатия кнопки вместе с индексом последовательности и шагом в качестве аргументов. Это отправит сообщение нашей функции обновления, но в этот момент обновление фактически ничего не сделает.
type Msg = ToggleStep Int Step renderStep index step = let ... in button [ onClick (ToggleStep index step) , class classes ] []
Сообщения являются обычными типами, но мы определили их как тип, вызывающий обновления, что является соглашением в Elm. В Update.elm мы следуем архитектуре Elm для обработки изменений состояния модели. Наша функция обновления примет сообщение Msg
и текущую Model
и вернет новую модель и, возможно, команду. Команды обрабатывают побочные эффекты, которые мы рассмотрим во второй части. Мы знаем, что у нас будет несколько Msg
сообщений, поэтому мы создали блок case, соответствующий шаблону. Это заставляет нас обрабатывать все наши случаи, а также разделять поток состояний. А компилятор будет следить за тем, чтобы мы не пропустили ни одного случая, который мог бы изменить нашу модель.
Обновление записи в Elm выполняется немного иначе, чем обновление объекта в JavaScript. Мы не можем напрямую изменить поле в записи, например record.field = *
, потому что мы не можем использовать this
или self
, но в Elm есть встроенные помощники. Имея такую запись, как brian = { name = "brian" }
, мы можем обновить поле имени, например { brian | name = "BRIAN" }
{ brian | name = "BRIAN" }
. Формат следует { record | field = newValue }
{ record | field = newValue }
.
Вот как можно обновить поля верхнего уровня, но с вложенными полями в Elm сложнее. Нам нужно определить наши собственные вспомогательные функции, поэтому мы определим четыре вспомогательные функции для погружения во вложенные записи:
- Один для переключения значения шага
- Один для возврата новой последовательности, содержащей обновленное значение шага.
- Другой, чтобы выбрать, к какой дорожке принадлежит последовательность
- И последняя функция для возврата новой дорожки, содержащей обновленную последовательность, которая содержит обновленное значение шага.
Мы начинаем с ToggleStep
, чтобы переключать значение шага последовательности дорожек между On и Off. Мы снова используем блок let...in
, чтобы сделать меньшие функции в операторе case. Если шаг уже выключен, мы делаем его включенным, и наоборот.
toggleStep = if step == Off then On else Off
toggleStep
будет вызываться из newSequence
. Данные неизменяемы в функциональных языках, поэтому вместо изменения последовательности мы фактически создаем новую последовательность с обновленным значением шага, чтобы заменить старую.
newSequence = Array.set index toggleStep selectedTrack.sequence
newSequence
использует Array.set
для поиска индекса, который мы хотим переключить, а затем создает новую последовательность. Если set не находит индекс, он возвращает ту же последовательность. Он полагается на selectedTrack.sequence
, чтобы узнать, какую последовательность изменить. selectedTrack
— это наша ключевая вспомогательная функция, используемая для доступа к нашей вложенной записи. На данный момент это удивительно просто, потому что наша модель имеет только одну дорожку.
selectedTrack = model.track
Наша последняя вспомогательная функция соединяет все остальные. Опять же, поскольку данные неизменяемы, мы заменяем всю нашу дорожку новой дорожкой, содержащей новую последовательность.
newTrack = { selectedTrack | sequence = newSequence }
newTrack
вызывается вне блока let...in
, где мы возвращаем новую модель, содержащую новую дорожку, которая повторно отображает представление. Мы не передаем побочные эффекты, поэтому снова используем Cmd.none
. Вся наша функция update
выглядит так:
update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleStep index step -> let selectedTrack = model.track newTrack = { selectedTrack | sequence = newSequence } toggleStep = if step == Off then On else Off newSequence = Array.set index toggleStep selectedTrack.sequence in ( { model | track = newTrack } , Cmd.none )
Когда мы запускаем нашу программу, мы видим визуализированную дорожку с рядом шагов. Щелчок по любой из кнопок шага вызывает ToggleStep
, который вызывает нашу функцию обновления, чтобы заменить состояние модели.
По мере масштабирования нашего приложения мы увидим, как повторяющийся шаблон архитектуры Elm упрощает обработку состояния. Знакомство с его функциями модели, обновления и просмотра помогает нам сосредоточиться на нашей бизнес-области и упрощает переход к чужому приложению Elm.
Взять перерыв
Написание на новом языке требует времени и практики. Первые проекты, над которыми я работал, были простыми клонами TypeForm, которые я использовал для изучения синтаксиса Elm, архитектуры и парадигм функционального программирования. К этому моменту вы уже достаточно узнали, чтобы сделать что-то подобное. Если вам не терпится, я рекомендую ознакомиться с Официальным руководством по началу работы. Эван, создатель Elm, расскажет вам о мотивах Elm, синтаксисе, типах, архитектуре Elm, масштабировании и многом другом, используя практические примеры.
Во второй части мы рассмотрим одну из лучших функций Elm: использование компилятора для рефакторинга нашего пошагового секвенсора. Кроме того, мы узнаем, как обрабатывать повторяющиеся события, использовать команды для побочных эффектов и взаимодействовать с JavaScript. Следите за обновлениями!