Мой хороший друг сказал, что мне нужно выучить вяз.

- «Вы многого от этого получите, не имеет значения, будете вы использовать это или нет».

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

Эльму 4 года, он взрослый, он транслируется в javascript и не испытывает усталости от выбора «правильного» фреймворка, потому что на самом деле это фреймворк. Он полностью функционален и является отцом redux:

Поэтому я пожертвовал несколькими обещающими быть хорошими уик-эндами, чтобы получить порцию идей, и теперь рад поделиться ими с вами:

1. Нет ошибок времени выполнения

Я начну прямо с хардкорных концепций.

Вы можете забыть о раздражающих сообщениях об ошибках NaN, Uncaught TypeError или foo is undefined в Elm, потому что он поддерживает проверку статического типа, что означает, что во время компиляции он проверяет весь ваш код на соответствие убедитесь, что все переменные и входы и выходы функций совпадают. Это означает, что если ваша функция принимает только String, вы не можете передать что-либо еще.

И хотя рекомендуется объявлять такие типы, как:

reps : String -> Int

(reps - это имя функции, String - это тип ввода, а Int - тип вывода), это не требуется, потому что Elm может автоматически вычислить все типы (что называется выводом типа) и показать вам соответствующие сообщения об ошибках или успешном завершении компиляции.

2. Типы союзов

Один хороший трюк, обычно используемый в Elm, - это тип Union, который позволяет вам определять возможные значения для странного или сложного типа. Допустим, у нас есть тип упражнения, который может быть одной из следующих строк: Pull-up или Push-up

type Exercise = Pull-up | Push-up

и хотите создать функцию reps, которая возвращает количество повторений в зависимости от упражнения:

reps : Exercise -> Int
reps exercise =
  case exercise of
    Pull-up ->
      10
    Push-up ->
      15

Здесь важно то, что всякий раз, когда мы передаем переменную Union в качестве параметра (в данном примере это exercise), мы вынуждены использовать выражение case. И что еще более важно, все ветви этого case выражения проверяются компилятором. Что обозначает:

  • если вы забудете обработать кейс (скажем, Muscle-up с 8 повторениями), вы получите ошибку во время компиляции.
  • если вы случайно наберете какой-либо регистр (скажем, Psuh-up), вы получите ошибку во время компиляции. Красиво, да?

3. Крайние случаи

И мы еще не закончили с типажами.

Даже для меня, который пришел в мир разработчиков после многих лет написания спецификаций требований и понимал важность крайних случаев, иногда трудно не забыть их в коде. Что делать, если значение равно undefined, или формат json неправильный, или время HTTP-запроса истекло?

Elm заботится об этом, вводя типы Maybe, Result и Task, которые являются частными случаями уже знакомых типов Union.

Если у вас есть необязательное поле, вы, вероятно, будете использовать Maybe:

type alias Sportsman =
  { name : String
  , age : Maybe Int
  }

это означает, что здесь поле age может представлять одно из двух значений: Nothingили Just Int - первое пустое и второе непустое значение. И поскольку это тип Union, каждый раз, когда вы передаете эту переменную в качестве параметра, вы вынуждены использовать выражение case и обрабатывать все варианты, поэтому вы не можете избежать обработки случая null / undefined:

getReps : Sportsman -> Maybe Int
getReps sportsman =
  case sportsman.age of
    Nothing ->
      Nothing

    Just age ->
      Just 12

Тот же подход используется для типов Result и Task, но вместо необязательных полей Task используется для операций синхронизации, таких как синтаксический анализ json, а Result используется для асинхронных операций, таких как html-запросы. Оба они могут иметь одно из следующих значений: Success для успешных случаев и Failure для ошибок. Как и в случае с Maybe, мы всегда вынуждены обрабатывать оба случая.

4. Распространение пакета

Еще одно последствие статической типизации - автоматическое принудительное управление версиями.

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

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

5. Redux

Redux - это хорошо известный шаблон в Javascript, в настоящее время зародившийся в Elm. Вы можете найти его в React и Angular, и я даже недавно использовал его в проекте jQuery без каких-либо современных фреймворков MV *.

Идея в том, что у вас есть одна точка доверия, которая называется Model (Elm) или State (React) или Store (Angular), и у вас есть интерфейс для ее обновления. В Elm мы называем это interfaceUpdate (который в мире Javascript является Reducer).

Поэтому мы вызываем функцию Update каждый раз, когда хотим изменить Model.

Функция обновления получает текущие Model и Msg (действие) в качестве параметров и возвращает новый Model.

В последнее время мы используем Model для обновления нашего представления, которое просто Html, если мы говорим о веб-сайте, и мы делаем это, вызывая одноименную функцию - View.

Вот как это выглядит:

А почему это круто?

6. Контроль качества и поддержка

Во-первых, QA становится намного проще.

Мы уже знаем, что Elm - это функциональный язык, что означает, что он предоставляет нам 2 функциональные гарантии:

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

И в основном это означает, что имея список Update вызовов функций, мы всегда можем их повторно запустить и всегда будем получать то же Model в результате повторного выполнения. Почему? Потому что никто не может изменить наш Model извне (помните? Никаких побочных эффектов) и потому что наша Update функция всегда возвращает тот же результат, если вызывается с теми же параметрами.

А это означает, что каждый раз, когда ваш QA-инженер обнаруживает ошибку, он отправляет вам журнал Update вызовов функций (который автоматически записывается) без ручного документирования всех шагов. И вы вместо того, чтобы вручную воспроизводить все шаги, просто импортируете его в свое приложение и сразу видите ошибку на экране с возможностью перемещаться вперед и назад по истории действий, чтобы увидеть, какая цепочка действий пользователя привела к ошибке. 🌟🌟

6. Ленивая загрузка

Второй плод использования redux - ленивая загрузка.

Что мне нравится в современных фреймворках javascript по сравнению с jQuery, так это то, что они декларативны. Вы не говорите в своей программе, как манипулировать DOM - добавлять, удалять, скрывать и т. Д. Вы просто описываете начальное и конечное состояния html - как он должен выглядеть в разных ситуациях, и фреймворк заботится (обычно с Virtual DOM) всего, что необходимо для обновления страницы, чтобы она выглядела так, как вы этого хотите.

То же самое и с Эльмом. Вы просто определяете View функцию, описывающую, как должен отображаться html на основе текущего Model. А затем, когда Model изменяется, ваша View функция обновляет DOM, соответственно сравнивая текущее и предыдущее состояния и выполняя соответствующие манипуляции с DOM.

А теперь интересный вопрос: что, если бы обновилась только небольшая часть Model, а остальная часть осталась неизменной? Есть ли смысл пересчитывать и сравнивать всю DOM? Конечно нет.

Если мы разбиваем нашу функцию просмотра на части, чтобы одна функция отображала заголовок, другая - нижний колонтитул и т. Д., То из-за гарантии первой функции (всегда возвращать тот же результат при тех же значениях аргументов) мы можем пропустить функции, входные данные которых не были измененный.

Мы просто помещаем ключевое слово lazy перед нашей функцией:

viewHeader: String -> Html Msg
viewHeader name =
    ...some code
view : Model -> Html Msg
view model = 
    lazy viewHeader model.loggedUser.name
    ...other code

Здесь у нас есть заголовок, который показывает имя пользователя, вошедшего в систему. После того, как что-то изменится в модели (пользователь прокручивает страницу вниз и загружается новый контент), мы не хотим пересчитывать часть заголовка модели DOM, потому что она в основном такая же, как и раньше. Но мы хотим обновить часть содержимого, потому что пользователь ее прокручивал. Это то, что lazy делает для нас - Elm проверит, что ввод viewHeader не был изменен, и полностью пропустит эту часть, но выполнит рендеринг остального. Это экономит ресурсы процессора и ускоряет рендеринг.

7. Неизменность

И это приводит к другому важному понятию Elm - неизменяемости. В предыдущем примере мы использовали String в качестве входных данных для нашей функции заголовка, но что, если бы у нас было что-то более сложное - Object?

Что, если мы хотим сравнить 2 входных объекта (новый и старый)? Затем мы должны углубиться в иерархию их полей и сравнить поля одно за другим. Это требует времени и ресурсов.

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

8. Еще быстрее

Но мы можем сделать еще быстрее.

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

В то же время мы хотим, чтобы другие операции, кроме перерисовки, работали в обычном режиме, будь то http-запрос или какие-то сложные вычисления в фоновом режиме.

Так как же добиться этого в Elm?

Что ж, на самом деле нам ничего не нужно делать. Он встроен. Elm использует requestAnimationFrame под капотом для синхронизации вычислений вида с перерисовкой браузера. Это означает, что функция просмотра, отображающая HTML, не будет вызываться чаще, чем 60 раз в секунду, и это экономит время, ресурсы ЦП и батарею мобильного устройства.

Основная причина, по которой мы можем сделать это в Elm, - это вторая гарантия функции, применяемая к функции View - она ​​не имеет побочных эффектов, поэтому единственное, что она меняет, - это html. Это означает, что он не может обновить Model. И хотя мы делаем View вызовы функций только 60 раз в секунду, наш Model по-прежнему обновляется в реальном времени без каких-либо задержек. Это чистота чистых функций.

9. Fuzz-тестирование.

Как мы уже видим, Элм прилагает много усилий для предотвращения ошибок в наших приложениях, поэтому используемая в нем среда тестирования также довольно сложна.

Одна функция, которая мне очень нравится и которая скоро появится в Mocha, - это тестирование фаззинга.

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

Fuzz.frequency
[ ( 1, Fuzz.intRange -100 -1 )
, ( 3, Fuzz.intRange 1 100 )
]

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

Заключение

Итак, когда мы узнаем все эти интересные вещи, как мы сможем использовать их в нашей обычной жизни с Javascript?

Во-первых, используйте статическую типизацию. Типичный сценарий или Flow могут быть отличными решениями для этого. TypeScript позволяет вам смешивать его с javascript в той же базе кода, что означает, что вы можете постепенно переводить свой проект в статический тип, не нарушая устаревший код. Он также обеспечивает отличную поддержку IntelliSense в сочетании с VSCode.

Во-вторых, используйте функциональный подход там, где это необходимо (управление состоянием, рендеринг и т. Д.). Библиотека Redux - отличный пример.

В-третьих, используйте неизменяемость с помощью готовых к использованию библиотек, таких как Mori или immutable.js, для улучшения обнаружения изменений и повышения производительности приложения.

Хакерский полдень - это то, с чего хакеры начинают свои дни. Мы часть семьи @AMI. Сейчас мы принимаем заявки и рады обсуждать рекламные и спонсорские возможности.

Если вам понравился этот рассказ, мы рекомендуем прочитать наши Последние технические истории и Современные технические истории. До следующего раза не воспринимайте реалии мира как должное!