Чтобы купить 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? Потому что это интересный вызов, редкие (но вкусные) вакансии и хороший…