Чтобы купить elm in action дешевле, можно зарегистрироваться на сайте и подождать. Скидки и акции там регулярно.
Конспект elm in action — это заметки, которые я записывал для себя, чтобы не забыть важные моменты. Этот материал отчасти черновик для будущих статей по Elm.
В хронологическом порядке. Версия книги v11.
Qualified и Unqualified доступ к тому, что было импортировано
После такой записи можно вызывать Array в unqualified стиле. Иначе без exposing, для указания типа «массив», вызывали бы в qualified стиле Array.Array
.
где PhotoSize
— custom type, а значения — variant
Maybe — это контейнер (привет, Профессор Фрисби), который может содержать 1 элемент.
Just value
— Just, это функция. С большой буквы, потому что указывает, что это Custom type + type variable.
Command — это некое значение, которое указывает elm runtime, что необходимо выполнить что-то. В отличии от функции, команда может возвращать разные значения при вызове с теми же аргументами. Функция нет.
Команды — это «сайд эффекты» в Elm.
Обычные же функции, являются чистыми (pure function).
Так как у update возвращаемое значение tuple, можно вернуть сразу и новую модель и команду. В таком случае:
Про тип данных tuple, можно найти в документации, в разделе Core language.
(детально на странице 80)
Кратко:
Cmd Msg
— значит, переменная у Cmd должна быть типа MsgCmd msg
— означает, что переменная у Cmd может быть любого типа, так как msg — это type variable (переменная типа, то есть может быть и строкой, и числом и т.д.)()
— это особый тип, называется Unit
, который сразу и тип и значение. Значением типа ()
может быть только ()
. На русский язык, это можно перевести как «ничто». То есть, функция getAnswer
принимает первым аргументом «ничто». Ничего. Значение, которое является ничем.
(Больше информации на стр. 84)
Обе записи выполняют одно дело: соединяют первый элемент + остальные в List. Но ::
эффективнее, так как не создается экстра-лист в процессе.
Предположим, у нас есть сообщение Loaded (List Photo)
, можно вытащить первый элемент и остальные из листа, с помощью конструкции Loaded (FirstPhoto :: OtherPhotos)
. Это можно назвать :: Pattern (стр. 96) и его аналог из мира JS — …rest.
Данная запись пригодилось в рандом генераторе, используемом через uniform, в который обязательно нужно передать первый элемент. Плюс в коде ниже, интересное использование Tuple.pair для возврата ( model, Cmd Msg )
и |> оператора (называется pipe (труба) оператор):
где errValue
и okValue
— type variable, и могут быть любого типа.
Type alias и custom type являются функциями-конструкторами. Есть статья на эту тему — An intro to constructors in Elm
Аргументы нужно передавать в таком же порядке, в котором они указаны в type.
Когда мы хотим обновить какое-то определенное поле в объекте, внутри какой-то структуры, нам до него не достучаться, как привычно в js, нужно создавать доп. переменные, перебирать и ловить с помощью фильтра нужное (как пример), и затем сохранять новое значение. Пример такого подхода, на странице 138 (Table 5.4 Updating the model).
Для порт-функции, которую мы будем использовать в JS, нам неважно, какой msg вернется. Поэтому, еще раз можно обратить внимание на разницу между Cmd Msg и Cmd msg (пункт Table 5.7 Comparing Cmd Msg and Cmd msg.)
И наоборот, когда мы используем подписку (Subscription), нам важен тип сообщения, так как мы будем посылать его в update функцию (пункт Table 5.9 Calling the activityChanges function, стр. 155)
Для того чтобы использовать ports (порты), не забываем указать в начале файла port
перед названием модуля (например: port module PhotoGroove exposing (main)
)
Если нужно вынести общий функционал, который используем при update, то можно сделать функцию (как обычно!), которая вернет model + Cmd Msg или что нужно, в зависимости от задачи. В принципе, тут все как в js, когда мы выносим функцию-хелпер.
можно использовать rAF, когда нам нужно запустить что-то из JS (например, подписка в порте) «после следующего» апдейта view в DOM. Пример в книге (стр 152, SYNCHRONIZING WITH THE ELM RUNTIME)
Asking «which approach rules out more bugs?» is a good way to decide between different ways to model data
Отвечая на вопрос «какое поведение выявит больше багов» можно выбрать среди разных подходов к созданию модели данных.
Техника интересная, направлена на то, чтобы помогать компилятору в будущем помогать нам. То есть, при создании определенной структуры (иногда, будет занимать больше кода) — компилятор будет ругаться, если в процессе рефакторинга мы передадим неверный тип переменной и т.д.
Из книги про ФП выяснилось, что есть некая структура «контейнер», и с помощью map мы можем взять значение из этого контейнера, изменить и положить обратно. Например, тип (контейнер!) List, мы взяли, изменили, положили обратно и там у нас так же List.
С остальными структурами такая же история. Но в некоторых случаях, map
может взять контейнер и не изменить в нем ничего.
То есть map
не работает с Err
кейсом у Result
, а только с Ok
. Следовательно значение в контейнере Err
вернется не тронутым. Удобно использовать в тестах.
Подробнее на странице 179 (pretty similar! Each map function…)
В тоже время, сигатура для .prop
— не просто тип этого свойства, а функция, которая это свойство из объекта вынимает. Пример с тестом:
testSlider "SlidNoise" SlidNoise .noise
Здесь SlidNoise
— строка, далее кастомный тип SlidNoise
, который ждет Int
, и .noise
— количество эффекта в модели.
Тип будет следующим:
(в книге — стр 190, this compiles because the SlidHue variant)
port module Main exposing (Model, Msg(..), Photo, initialModel)
Если пишем Msg(..)
то значит мы «expose» (выставляем наружу) так же и все варианты (variant) нашего типа Msg
.
Единственный смысл в «expose» просто Msg
, это использование типа в описании, например: Msg -> Model. Так же, здесь можно отметить технику opaque types, которая в книге не упоминалась.
По коду книги, написать Photo(..)
— мы не можем, так как в примере у Photo
нет вариантов.
Документация elm-test
npm install -g elm-test
Команда elm-test init
создаст директорию с тестами и скачает нужные пакет(ы).
Название файла в директории тестов, должно быть таким же, как название тестируемого модуля.
import Fuzz exposing (Fuzzer, list, int, string)
fuzz позволяет генерировать рандомные значения.
Можно написать свой fuzzer, пример:
Где urlFuzzer
— кастомный fuzzer
.
Тестирование view мне не понравилось. Я бы лучше использовал e2e тестирование на cypress.
Можно инициировать с помощью Decode.succeed
comparable, number, appendable
Если хотим в аннотации к функции хотим использовать разные number, то можно сделать так:
В аннотации (сигнатуре) функции нельзя использовать number, так как выше указаны 3 зарезервированных type variable.
Когда у нас есть два схожих case, которые пытаются обработать Nothing
, мы можем использовать Maybe.andThen
Подробнее: стр. 216-217 (refactoring to use Maybe.andThen)
andThen принимает callback
и Maybe a
значение, а затем возвращает Maybe b
, если в a
не было Nothing
, иначе Nothing
.
Когда вы указываете type alias, то компилятор «разворачивает» каждый alias в реальный тип и в таком случае нет возможности указать рекурсивный тип (компилятор уйдет в бесконечную рекурсию). На помощь приходит кастомный тип с одним вариантом. Например:
Затем такой variant можно вытащить в одну строку, вместо записи через case of:
Вместо:
Можно передать record. В таком случае и порядок следования аргументов не важен (в JS это передача в функцию объекта со свойствами, а не аргументов через запятую):
Для рекурсивных декодеров пригодится Decoder.lazy, так как он остановит компилятор от бесконечной попытки «раскрыть» тип декодера:
Foldl — тоже самое, что reduce в JS, foldr — reduceRight
foldl и foldr есть у большинства структур, не только у List.
Если нужно объединить данные в один dict без дублирования ключей — dict.union наш выбор.
If there is a collision, preference is given to the first dictionary.
Если ключ будет присутствовать в первом словарике и во втором, то предпочтение будет отдано первому. То есть, значение из первого словаря будет в финальном словаре (Dict).
Html.lazy может помочь. Подробнее в документации и в книге в разделе 8.1.3 Skipping Unnecessary Renders with Html.Lazy
Похоже на shouldComponentUpdate из react: указываем изменение какого параметра в модели нам интересно и все остальное будет игнорироваться.
Если бы было (s "photos" "car")
, то это был бы не параметризированный url-адрес «photos/car».
В oneOf у парсера мы можем держать список всех доступных URL-адресов приложения.
Паттерн называется — «таблица правды» (truth table) и его цель — перечислить все возможные варианты.
Ниже пример, который показывает состояние ссылки — активная или нет, в зависимости от страницы. В приложении есть страницы и «подстраницы», поэтому в таблице как раз указаны возможные комбинации.
(стр 281)
Вопрос, который долго меня мучал как новичка — в чем разница между Html Msg
у родителя и Html Msg
у ребенка.
Так как книга подходит к концу, суть уже ясна: модуль Main
имеет свой тип Msg
, и поэтому когда мы вызываем дочерний_модуль.view
— то функция возвращает Html Msg
дочернего модуля. Для модуля Main
возвращаемое значение будет уже типа Html Children.Msg
.
Как итог, получаем несоответствие типов, так как у Main
view
сигнатура: Model -> Html Msg
Как быть? Как всегда — с помощью .map
превращаем из А в Б.
В процессе работы над Elm in action, получается приложение близкое к фотогалерее и фоторедактору, очень упрощенным, конечно. Тем не менее, в книге очень хорошо описаны основные понятия и, как говорит сам Ричард, нет умных слов: монады, теория категорий и прочее…
Рекомендую, мне книга очень понравилась.
Итоговый код можно посмотреть здесь.
Сегодня будем использовать parcel и IntelliJ IDEA Community Edition. Все инструменты бесплатные. Инициализация elm проекта…
На данном вебинаре мы знакомились с языком Elm проводя параллели между Elm и Redux, поэтому…
Richard Feldman рассказывает как масштабировать Elm приложение без боли. Показаны техники: extended records, подход narrow…
В данной заметке вы найдете конспект видео по Elm, которые я посмотрел в ноябре 2019.…
Итоги года 2019 // Max Frontend Покажи мне свой гитхаб, и я скажу работал ли…
Почему стоит изучать Elm? Потому что это интересный вызов, редкие (но вкусные) вакансии и хороший…