Появление Code 2018 в Elm Review

Введение

Я недолго участвовал в Пришествии кода 2018. Каждый год они публикуют 31 кодовую головоломку, по 2 в день. Вы должны решить их, прежде чем вы сможете перейти к следующему. Я хотел написать о том, что я узнал. Я никогда раньше не участвовал и хотел использовать это как предлог, чтобы заставить себя использовать язык функционального программирования. Я использую концепции функционального программирования в своей повседневной работе, но у меня никогда не было возможности погрузиться и заставить себя решать более сложные задачи на чистом языке FP. Это было вдвойне тяжело, потому что упражнения — это НЕ то, чем я занимаюсь на работе, и они сложны. Хотя они были очень тяжелыми в забавном смысле. Ниже я расскажу о 6 упражнениях, которые я сделал (я бросил полотенце на 7-й день), и объясню некоторые интересные нюансы, которые я обнаружил в упражнении и мышлении в FP… и мышлении в Elm.

Почему Эльм?

Elm — это язык Функционального программирования для создания веб-приложений. Это похоже на Haskell, но в нем практически нет чепухи Теория категорий, и он как бы адаптирован для традиционных UI/веб-разработчиков. Учитывая, что я какое-то время отсутствовал во фронтенд-разработке, имея дело с Python, Node и Go в течение последних 2 лет, я решил, что Elm — отличный выбор, чтобы вернуться во фронтенд. В прошлом у меня были проблемы с Elm, особенно со случайными числами, и с выяснением того, что означают ошибки типа компиляции.

Общие проблемы с Elm

У меня было несколько проблем с языком Elm, которые я хотел бы выделить, и которые показались мне интересными. В прошлом я играл с Elm пару раз, и хотя время, проведенное вдали от изучения FP, очень помогло, у меня все еще были проблемы.

Вне практики с типами

Когда я думал, что TypeScript — это круто, остальная индустрия JavaScript — нет, поэтому я научился жить без типов в течение длительного времени. Я пришел из ActionScript 2 и 3, которые похожи на Java, поэтому я потратил годы на то, чтобы привыкнуть к типам и был доволен ими. Приступая к JavaScript, индустрия не была готова к ним, поэтому я научился обходиться без них.

Возвращаться назад очень сложно после нескольких лет работы с JavaScript, Lua, Python и даже короткого пребывания в Go. Попытка сделать каррирование в Go было упражнением в чистом мазохизме, тогда как в Elm это встроено и отходит на второй план, почти незаметно. Я думаю, что в 6 упражнениях я использовал 1 частичное приложение без компоновки (что в Elm не особо считается).

Моя главная проблема с типами — это игра с идеями, которая теперь имеет другую, огромную цену. Меня волнует только то, работает ли код в основном; Я просто пытаюсь проверить некоторые идеи и подходы. Elm и, в частности, система типов Hindley-Milner помогают гарантировать отсутствие ошибок во время выполнения, что означает, что они не наносят ударов, сообщая вам, что ваш код неправильный. Здесь нет any, как в TypeScript. Хотя это отлично подходит для производственного кода, это ужасно, когда у вас есть день, чтобы проверить решение (я) и придумать в основном рабочий прототип.

Снова и снова я запускал JavaScript и Node либо локально, либо в Code Sandbox IO, чтобы поиграть с идеями и протестировать алгоритмы, ТОГДА я переписывал в Elm. Начинать в Elm было сложнее для некоторых алгоритмов, которые я пока не мог понять.

Никаких циклов, только рекурсия

Поскольку Elm является чисто функциональным, в нем нет циклов для программирования. Вы можете использовать только рекурсию, чтобы избежать побочных эффектов. Учитывая, что многие упражнения Advent of Code 2018 были связаны с многократным синтаксическим анализом больших массивов/списков, очень часто можно было увидеть, как решения JavaScript и Python используют while(true) с break, когда выполняется какое-то условие, и часто вложенные for циклы.

Вяз не имеет петель. Вместо этого вы либо используете ручную рекурсию, когда функция вызывает сама себя, либо оператор case (который не является оператором переключения JavaScript и больше похож на случай сопоставления шаблонов Elixir). Ниже я приведу несколько примеров. Я указываю на это, потому что такой способ мышления СУПЕРСЛОЖЕН после 15 с лишним лет моего мозга, запрограммированного на циклическое и вложенное циклическое мышление. Вы делаете много этого в программировании графического интерфейса для макета, и удивительно, как трудно было рекурсивно думать даже о простых вещах.

Синтаксис Пробелы

Elm почти так же строго относится к пробелам, как и Python. Я говорю почти, потому что до сих пор неясно, когда оператор return полностью сломает компиляцию или все в порядке, и вдруг все станет намного читабельнее. Мне не удалось найти хорошее руководство о том, что вам разрешено делать, и предложения после запятых перед элементами в списках.

Я поднимаю эту тему, потому что…

Подробные анонимные функции

… анонимные функции немного многословны. Они вам НУЖНЫ, когда вы играете с идеями, особенно когда вы портируете с Python или JavaScript. Я имею в виду не только идеи, я также имею в виду конкретные типы. Вам может нравиться ваша цепочка синтаксического кода… но вы еще не уверены, собираетесь ли вы коммитить список или массив, и вы еще не знаете, какие типы будут в этом списке. Функции анонимности обеспечивают большую гибкость в Elm в сочетании с операторами let для выхода из системы, прежде чем вы будете готовы зафиксировать типы и более обычные, неанонимные функции.

Теперь сам синтаксис приятный: (\arg -> arg + 1)

Сравните это с JavaScript: arg => arg + 1

Однако, как только вы начнете выхватывать функцию foldl (версия Elm Array.reduce), все может стать довольно корявым для чтения довольно быстро. Я так и не нашел здесь хорошего решения/соглашения о размещении пробелов, кроме преобразовать его в нормальные функции, чтобы вы действительно могли его прочитать. Ниже я покажу несколько примеров размазанных анонов, и вы скажете: Ох… мерзко, рефакторинг, чувак!

Приближаемся к Full Lisp с тоннами скобок

Применение концепций FP в JavaScript привело к тому, что мой JavaScript стал очень похож на Lisp раздражающим образом: куча скобок из-за каррированных функций повсюду.

.then( value => something(JSON)(request)(value) )

Даже когда я удалил анонимные функции, у меня все еще были вложенные функции, и иногда Elm не понимает, что вы имеете в виду, поэтому вам приходится использовать круглые скобки, чтобы сказать это точно. Тем не менее, НАМНОГО предпочтительнее добавить их позже, чем требовать, как в программировании JavaScript/Python FP.

Здесь у нас большой связанный набор функций, и круглые скобки совсем неплохие:

А здесь я просто создаю простой тип с параметрами… но у меня есть функции для обеспечения их преобразования и… бле:

Опять же, гораздо лучше, чем JavaScript, я понимаю, что это супер придирки.

Окраска инструментов

Мне всегда нравилось окрашивание кода; это помогает мне быстро увидеть различные части кода и понять, что делает функция. Однако мой текущий плагин Elm для VSCode, похоже, не правильно выделяет методы, а только модули. Это становится проблемой, когда вы импортируете как Array, так и List, и у них много одинаковых имен методов, таких как map. Чтобы убедиться, что вы не получите ошибку компиляции, проще добавить к методу префикс модуля, который вы начинаете делать с несвязанными вещами, такими как Maybe, просто для согласованности. Однако вы делаете свой код более подробным.

Обратите внимание, что ни один из методов, как моих, так и нативных, не окрашен; того же серого цвета. Это останется, если вы опустите List и Array, что приведет к следующему:

Кратко, да, читабельно… конечно, но я хочу, чтобы мои цвета были такими же, как у меня со всеми хорошими плагинами для раскрашивания кода Python и JavaScript. Он также запутался бы в том, где именно в коде находится ошибка, когда щелкал ошибки в области компиляции/ошибок и прокручивал до неправильной части. Тем не менее, мне понравилось красное подчеркивание проблемных мест + с правой стороны, указывающее, где в коде были проблемы.

Набор инструментов для современной веб-компиляции

В JavaScript вы можете посвятить всю свою карьеру D3.js, визуализируя данные. То же самое можно сказать и о компиляции JavaScript с использованием Babel и Webpack вместе. Для тех из нас, кто пришел из мира, где компании создают все это для вас, например, C, Visual Studio Code для C#, ActionScript в Flash/Flash Builder Eclipse, а в настоящее время Create React App, Angular CLI и Vue CLI … смешно думать, что все мы снова заинтересованы в создании пользовательской цепочки компиляции для Elm в браузере. Для всех нас у нас есть одна и та же общая потребность: преобразовать этот Elm в JS и добавить index.html.

Мне кажется, что Elm Reactor никогда не работает, и хотя вы можете немного выжить, используя только файл Main.elm, в конечном итоге вам понадобится базовый index.html... как... знаете, любое другое веб-приложение сегодня. Я предпочитаю, чтобы Facebook создавал, обновлял и поддерживал мою цепочку инструментов компиляции (создавал приложение для реагирования), пока я сосредотачиваюсь на создании вещей. В Elm этого еще нет (или я просто хреново разбираюсь в Elm Reactor), поэтому я взломал минимально необходимый Webpack и просто копировал пасту между проектами. Я упоминаю об этом, потому что большая тройка недавно поняла, что вам нужен cli, чтобы делать это для разработчиков, и без него требуется огромная трата времени + дублированные усилия. Игра в ожидание имеет некоторые преимущества, например, ES6 ПОЧТИ работает в современных браузерах, хех, но... для тех из нас, кто хочет кодировать и развертывать сегодня... нам нужно лучше.

Подсказка типа «ОК»

Учитывая, что мы используем одну из лучших в своем классе систем типов, не должны ли подсказки типов, которые я получаю при использовании функции или модуля, также быть лучшими в своем классе? Очевидно нет. Посмотрите, как я пытаюсь использовать ранее определенную функцию и тип; оба дают мне жалкие подсказки кода. Я видел, как это иногда работает; возможно, мне следует использовать какое-то другое расширение или что-то другое, кроме VSCode.

параллелизм

Многие из упражнений предназначены для того, чтобы взволновать любителей производительности. Как FP-программист динамических языков, последнее, что меня волнует, — это производительность… потому что это не моя проблема. Я создаю веб-приложения для отображения 20 элементов в таблице или извлекаю 12 КБ JSON из веб-службы Lambda. Однако даже некоторые базовые упражнения дадут вам от десятков тысяч до сотен тысяч итераций. Я слышал от своих друзей, которые были намного дальше, что их миллионы, и они задавались вопросом, как их Python будет сравниваться с такими, как Go и Rust для некоторых упражнений.

Я заметил, что пользовательский интерфейс блокировался для большей части моего кода. Теперь, даже низко висящие плоды использования List вместо Array или Set вместо Array я мог бы получить большие выгоды, но даже маленькие вещи я тоже проигнорировал. Возможно, это очень помогло бы, но больше 10 000 итераций я сильно в этом сомневаюсь. Поскольку я создавал пользовательские интерфейсы для всех своих упражнений, и ни один из моих циклов не нуждался в вводе-выводе, это идеальный вариант использования WebWorkers. У Elm есть первоначальный Process API, но в документации он звучит так, будто он еще не готов. Единственный раз, когда длинные циклы сломали мой браузер, было то, что я регистрировал так много JSON, что у Chrome закончилась память. Однако, если я отключил журналы, дни 1–6 прошли за разумное время. Как разработчик пользовательского интерфейса, я знаю, что блокировка пользовательского интерфейса — это любительский час, учитывая API, который у нас есть в браузере, поэтому я хотел бы выяснить, как правильно сделать это в Elm.

День 1–6 Обзор

Ниже я приведу код для каждого упражнения. Хотя вы можете прочитать код, чтобы увидеть, как я решал задачи самостоятельно, я просто освещаю те части, которые мне показались интересными при изучении Elm и FP в целом.

Elm широко использует оператора конвейера; вы можете узнать больше об этом, если использование в приведенном ниже коде не имеет смысла.

День 1: парсер частоты

Исходный код | Ссылка на приложение

День 1, Задача 1 была довольно простой: разобрать список положительных и отрицательных целых чисел. Решение в основном состоит в том, чтобы взять все эти числа и сложить их. Вот для чего были созданы списки материалов:

frequency = List.sum list

Примерно так же, как sum в Лодаше:

const frequency = sum(list)

Тем не менее, вызов 2 вы должны найти, когда он достигает одного и того же числа дважды. Самый простой способ в императивном кодировании — просто использовать while(true) и использовать JavaScript Set, чтобы гарантировать отсутствие дубликатов и просто увеличивать счетчик. Тем не менее, это Elm, так что это был мой первый опыт использования рекурсии для решения проблемы. Это также было первым признаком шаблона: foldl используется для всех вещей. Серьезно, поскольку большинство задач заключалось в циклическом переборе множества элементов и их изменении, вы в конечном итоге используете foldl как средство для всего.

Это также был первый шаблон из многих, когда я сдался, выкинул JavaScript, а затем вернулся в Elm. Скорость, с которой вы можете быстро создать прототип 1 строки кода или 1 алгоритма, я не могу достичь в настоящее время в Elm, даже в версии elm. Браузерный редактор Elm Ellie App, безусловно, помогает, но не так быстро, как Node или Python в командной строке.

В итоге получилось:

matchOrNot = reduceUntilMatch  { matched = False, frequency = 0, match = 0, set = Set.singleton 0 } list

Где reduceUntilMatch продолжает вызывать себя, пока не найдет совпадение. Все функции сокращения имеют начальное состояние, и тот тип MatchOrNot, который я создаю, был в основном набором из 4 переменных, которые я создал в циклической версии JavaScript, хех.

День 2: Анализатор контрольных сумм

Исходный код | Ссылка на приложение

Я не очень разбираюсь в контрольных суммах, но второй день был единственным, через который я справился без долгих размышлений, отладки в течение нескольких дней и/или использования JavaScript или Python.

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

filterOutSameLetters charList =
    List.map (partitionSameLetters charList) charList
        |> List.filter (\matchList -> List.length matchList > 1)

Обратите внимание, что выше filterOutSameLetters нет определения типа, и я не тратил время на определение функции фильтра.

В-третьих, Maybes начал раздражать. Большинство систем типов не могут вводить в ОЗУ такие вещи, как «что такое элемент 3 в списке строк?». Вместо этого они дают вам значение Maybe. Однако, как только вы начнете сочинять вещи, вы можете начать иметь дело со значениями по умолчанию, что может быть проблемой. Вы действительно действительно должны думать о разветвлениях, как в момент, когда вы даете значение по умолчанию, так и позже: как это влияет на правильность моего алгоритма? Вкратце у меня были эти проблемы с JavaScript, но я занимаюсь разработкой пользовательского интерфейса или API, а не машинным обучением/наукой о данных, поэтому я не имею дело с таким уровнем корректности данных в своей работе. Здесь, в Elm, с типами, это усилилось.

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

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

filteredMatches =
    Array.filter (\item -> item.match == True) matches
filteredMatcheslog = log "filteredMatches" filteredMatches

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

const doStuff = arg => someFunction(arg)

Однако, как только что-то покажется странным, они (включая меня) вернут его обратно, чтобы отладить происходящее:

const doStuff = arg => {
    console.log("arg:", arg)
    const result = someFunction(arg)
    console.log("result:", result)
    return result
}

Не знаю почему, но я чувствовал себя гораздо менее виноватым, используя let в Elm.

День 3: Парсер претензий

Исходный код | Ссылка на приложение

Я видел, как утверждалось, что основное различие между старшим разработчиком и младшим заключается в том, что старший разработчик, когда он пишет строку кода, будет жить дольше, чем младший. Младший напишет его, изменит, а затем удалит в рамках более крупного рефакторинга. День 3 подтвердил, что я действительно джуниор в Elm. Проведя 5 дней и, наконец, разобравшись в Python и JavaScript, я переписал свою 4-дневную работу в Elm и начал с нуля, потому что созданные мной алгоритмы было слишком сложно распутать. Я давно не чувствовал этого n00b, который был отчасти крутым, но унизительным.

Упражнение состоит в том, чтобы вычислить, где нарисованы все прямоугольники, а затем перекрытие и самое большое перекрытие. Я использовал Canvas для визуализации, так как я чувствую себя там как дома и мне нравится видеть, как мои алгоритмы работают или терпят неудачу. Это и самое главное, я всегда мечтал о том, какой будет разработка пользовательского интерфейса без состояния, особенно отрисовки вещей. Я не знаю, как бы я сделал это в JavaScript, но в Elm это выглядит очень естественно и не так чуждо, как я думал.

Определять типы в Elm легко, но создавать их сложно.

type alias Claim = 
    { id : Int
    , rectangle : Rectangle 
    , calculated : Bool
    , overlaps : Bool }
type alias Rectangle =
    { left : Int
    , top : Int
    , width : Int
    , height : Int
    , right : Int
    , bottom : Int
    , overlaps : Bool }

Я начал использовать вспомогательные функции, чтобы просто создавать и уменьшать количество скобок, которые мне приходилось использовать. Например, создание Rectangle, а затем создание Claim, которое составляет прямоугольник внутри него.

-- easier if I make a function so I can generate the right and bottom properties when you make the rectangle
getRectangle : Int -> Int -> Int -> Int -> Rectangle
getRectangle left top width height = 
    Rectangle left top width height (left + width) (top + height) False
-- easier to create a claim this way, less parameters
getClaim : Int -> Int -> Int -> Int -> Int -> Claim
getClaim id left top width height =
    Claim id (getRectangle left top width height) False False

Похоже на шаблон, но, чувак, использовать его было так же просто, как:

getClaim id left top width height

К сожалению, я все еще приводил целые числа к числам с плавающей запятой, когда рисовал их на холсте (обратите внимание на toFloat ):

renderFilledRectangle x y width height color cmds =
    cmds
    |> Canvas.fillStyle color
    |> Canvas.fillRect (toFloat x) (toFloat y) (toFloat width) (toFloat height)

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

Наконец, в День 3 типы в Elm действительно доказывают, почему вы не можете получить ошибки времени выполнения. Для преобразования String JSON в тип Elm по сравнению с нулевыми усилиями со стороны JavaScript требуется день и ночь… например, 1 строка JavaScript против 88 строк Elm. В JavaScript вы просто читаете файл через fs.readFileSync, а затем через JSON.parse.

Однако в Elm JSON.parse полон побочных эффектов, поэтому они заставляют вас использовать низкоуровневые примитивы, и вам приходится анализировать свой путь вверх. Вы можете либо анализировать необработанные строки, что я делал в День 3, либо использовать их механизм синтаксического анализа, о котором я расскажу в другом упражнении ниже. Вот разбор одной претензии.

parseClaimStrings : Array String -> Claim
parseClaimStrings stringArray =
    let
        id =
            Array.get 0 stringArray
            |> Maybe.withDefault "#0"
            |> String.replace "#" ""
            |> String.toInt
            |> Maybe.withDefault 0
        left =
            Array.get 2 stringArray
            |> Maybe.withDefault ","
            |> String.split ","
            |> Array.fromList
            |> Array.get 0
            |> Maybe.withDefault "0"
            |> String.toInt
            |> Maybe.withDefault 0
        top =
            Array.get 2 stringArray
            |> Maybe.withDefault ","
            |> String.split ","
            |> Array.fromList
            |> Array.get 1
            |> Maybe.withDefault "0"
            |> String.replace ":" ""
            |> String.toInt
            |> Maybe.withDefault 0
        width =
            Array.get 3 stringArray
            |> Maybe.withDefault "x"
            |> String.split "x"
            |> Array.fromList
            |> Array.get 0
            |> Maybe.withDefault "0"
            |> String.toInt
            |> Maybe.withDefault 0
        height =
            Array.get 3 stringArray
            |> Maybe.withDefault "x"
            |> String.split "x"
            |> Array.fromList
            |> Array.get 1
            |> Maybe.withDefault "0"
            |> String.toInt
            |> Maybe.withDefault 0
    in
        getClaim id left top width height

Я знаю, что это немного безумно, так что давайте просто возьмем часть width и сравним ее с JavaScript, чтобы увидеть, с чем мы имеем дело. В обоих языках у вас есть строка, которая выглядит как #147 @ 570,688: 27x25. Это претензия; идентификационный номер с позициями x и y, разделенными запятой, и, наконец, ширина и высота, разделенные на x. Для ширины нам нужно это число 570. Как для Elm, так и для JavaScript, если вы разделите это на пространство, вы можете выбрать то, что вам нужно, из массива по индексу. то есть 0 будет идентификатором, 2 будет x и y вместе, а 3 будет шириной. Похоже на 27x25. Разбирая это с чистыми функциями, которые не могут выдавать ошибки, давайте разберем алгоритм:

width =
    Array.get 3 stringArray
    |> Maybe.withDefault "x"
    |> String.split "x"
    |> Array.fromList
    |> Array.get 0
    |> Maybe.withDefault "0"
    |> String.toInt
    |> Maybe.withDefault 0
  1. Array.get 3 stringArray дает вам Maybe; это будет либо Just(27x25), либо Nothing
  2. Из-за Nothing мы говорим "хорошо, по умолчанию с "x", тогда", чтобы вы могли разделить и ничего не получить в дальнейшем", используя Maybe.withDefault "x"
  3. String.split "x" даст нам список из 2 элементов. Если это просто 'x', это будет список с двумя пустыми строками... иначе это будет ["27", "25"]
  4. Однако вы не можете получить отдельные элементы в List (список не имеет метода get), поэтому нам нужно преобразовать в Array, чтобы мы могли получить первый элемент, ширину. (Джесси, почему ты не использовал «List.headПотому что я n00b!)
  5. … однако (опять же), Array.get 0 дает нам Maybe ; либо Just("27"), либо Nothing, так что... по умолчанию мы используем '0', зная, что позже попытаемся преобразовать строку в целое число.
  6. String.toInt даст вам еще один кровавый Maybe, потому что нет никакого способа гарантировать, что String может быть проанализирована как целое число с использованием типов, поэтому... по умолчанию мы используем значение 0.

JavaScript? parseInt(str.split(' ')[3].split('x')[0]) хотя несколько реализаций JavaScript и Python, которые я видел, даже не анализировали его; они просто использовали эти строки для поиска в объекте/словаре.

Теперь хорошие новости: весь конвейер Elm безопасен с точки зрения типов. Это означает, что я не получу никаких исключений во время выполнения, независимо от того, насколько сильно искажена строка. Плохая новость заключается в том, что вам действительно нужно подумать о значениях по умолчанию для Maybes. Что, если, например, 0 действительно является допустимым значением? Я, вероятно, должен был использовать Результат, чтобы быть более строгим с разбором. Однако вы можете видеть, что если предположить, что ваши данные статичны, то JavaScript будет намного более кратким и с ним легче играть с идеями, если вы не заботитесь о типах и в целом знаете, что с вашими данными все в порядке.

Одно замечание заключается в том, что Elm Canvas версии 3 использует requestAnimationFrame для эффективного рендеринга, но не работает в приложении Ellie. Версия 2 работает в приложении Ellie И не требует, чтобы ваше приложение Elm заботилось о подписках (вспомните WebSockets в Redux). Так что я остановился на v2. Несмотря на то, что они оба требуют конвейера JavaScript (второй способ, который я нашел для сбоя приложения Elm, написав дрянной JavaScript в конвейере), у меня вообще никогда не было проблем с Canvas; замечательная библиотека.

День 4: Расписание сна

Исходный код | Ссылка на приложение

День 4 был посвящен анализу времени сна. Как и многие программисты, я боюсь всего, что связано с математикой даты. Elm, в отличие от JavaScript, имеет только время Posix, и… ну, API отстой по сравнению с JavaScript, особенно с чем-то вроде Moment или date-fns. Есть несколько библиотек дат в Elm, но я обнаружил, что могу сам заняться целочисленной математикой.

Я изо всех сил пытался правильно отсортировать, поэтому зашел в Elm Slack, где у них была комната Advent Of Code 2018, чтобы посмотреть, как другие подходят к этому. Я заметил, что один разработчик использовал другой тип синтаксического анализа строк, который я раньше не видел в библиотеке синтаксического анализатора. Он разбирает строку символ за строкой, но поддерживает ветвление в случае, если строки не выравниваются из-за пробелов или по какой-либо другой причине. Я использовал это как возможность изучить его по сравнению с моим традиционным фу String to Array. Утверждения типа case, которые он поддерживает,… интересны. Он казался более гибким и созданным для синтаксического анализа, чем старый добрый String.split.

Мне также понравилось некоторое равенство, которое я видел, чтобы сравнивать вещи. Поскольку я придумал свой собственный тип Date, мне нужно было понять, как определить, больше ли он, равен или меньше чего-либо. Elm не имеет valueOf по соображениям чистоты, но у них ДЕЙСТВИТЕЛЬНО есть тип Order, вы можете использовать функцию compare. Мой мозг начал плавиться (обычное явление во время этих упражнений), как только мне пришлось сравнивать более глубокие части даты, если что-то совпадало.

Например, сравнить 2017 год с 2018 годом довольно просто:

compareDateTimeYear : DateTime -> DateTime -> Order
compareDateTimeYear a b =
    compare a.year b.year

Если мы вызовем compareDateTimeYear с 2017 и 2018, он вернет LT. Но как сравнить всю дату? Вы делаете кучу этих функций сравнения на кусочках, ЗАТЕМ помещаете их в большой вложенный оператор case:

compareDateTimes : DateTime -> DateTime -> Order
compareDateTimes a b =
    case compareDateTimeMonth a b of
            EQ ->
                case compareDateTimeDate a b of
                    EQ ->
                        case compareDateTimeHour a b of
                            EQ ->
                                compareDateTimeMinute a b
                            _ ->
                                compareDateTimeHour a b
                    _ ->
                        compareDateTimeDate a b
            _ ->
                compareDateTimeMonth a b

Она большая «мех» с точки зрения вложенности, но я был горд, что сделал ее, и она работает.

Наконец, это был мой первый опыт Tuples, и сначала я думал, что они самые странные и глупые, но я ошибался… о них позже.

День 5: Анализатор полимеров

Исходный код | Ссылка на приложение

День 5 был в основном разбором писем. Как парсер контрольных сумм из Day 2, не знаю почему, но этот мне показался простым. По какой-то причине рекурсия здесь имеет больше смысла, поскольку вы в основном «плаваете снова и снова в большой строке, которую продолжаете сжимать». Я всегда могу визуализировать рекурсию и разбор строк.

Единственное, на что я укажу в День 5, это то, что Elm в настоящее время не сообщает вам, когда у вас мертвый код. Для дня 5 это нормально, так как даже с неиспользованным импортом это все еще небольшой файл. В JavaScript последняя версия VSCode затемняет переменную, показывая, что она не используется. Однако иногда у вас может быть код, который, как вы ДУМАЕТЕ, используется, но это не так. Я бы хотел, чтобы был либо визуальный элемент, как в JavaScript, либо какой-то переключатель компилятора для него.

День 6: Поиск координат

Исходный код | Ссылка на приложение

Шестой день был одним из самых сложных. Моя математика продолжала немного отставать, и я еще не научился извлекать небольшие части моих алгоритмов… что, кстати, как раз и есть функциональное программирование; маленькие, многоразовые функции, и все же здесь я борюсь, ха! Я упомянул коллегу Python, и он сделал один простой трюк, загрузив 4 координаты вместо всех 50. Это сделало мой код намного быстрее и проще для тестирования. Я также определил, что я был там на 99%, у меня была только одна неверная строка кода, говорящая, что координата не принадлежит.

Оглядываясь назад, если бы я знал заранее, что они ищут графа Вороного, я бы начал с него первым. Тем не менее, я допустил несколько довольно выглядящих ошибок.

Опять же, я обнаружил, что добился большего прогресса в прототипировании алгоритмов в JavaScript, чем в Elm, главным образом потому, что его было легче отлаживать, быстрее играть без кричащих на меня типов, и я мог быстро тестировать и создавать новые сценарии. Возможно, со временем это изменится, как это произошло с ActionScript 2 и 3, и типы просто стали «второй натурой» при написании кода.

Единственное, что мне понравилось в Дне 6, помимо создания крутых визуализаций, — это знакомство с Кортежами. Они похожи на Elm List, но вы можете получить из них значения, и вы не получите Maybes обратно! Это делает работу с ними очень удобной и требует намного меньше кода. Сначала это казалось странным, особый тип списка, содержащий только 2 элемента, и я думал, что это так глупо, но потом я начал видеть их повсюду и попытался встроить их в свои типы только потому, что они того стоили, потому что их было легко понять. использовать. Подумайте о возвращаемых значениях функции {:ok, value} в Elixir или err, value в Go, и вы это поймете.

Выводы

Появление Code 2018 было непростым, оно довольно продвинутое. Типы проблем — это не то, с чем мне когда-либо приходилось сталкиваться за 19 лет разработки программного обеспечения. Вкупе с попыткой выучить новый язык с новым мышлением о программировании это был рецепт боли. Я рад, что сделал это.

Я убежден, что Elm для интерфейса по-прежнему лучший. Мне нравится React + Redux, но волшебство и отсутствие детерминизма и проблемы тестирования JavaScript в целом всегда раздражали меня, КОГДА Я УЗНАЛ, что есть другой путь. Для контекста, я был сделан и счастлив в моем контроллере представления модели, мире объектно-ориентированного программирования сильных компиляторов и типов среды выполнения еще во времена Flash и Flex. Перенесение того, что я узнал из FP и даже Elm, обратно в JavaScript, как мне показалось, привнесло в мой код сильный уровень детерминизма, упростило его тестирование и значительно упростило обвинение серверной части или операционной системы. все мои проблемы. Хотя написание пользовательского интерфейса в Elm без компонентов может быть болезненным (например, инвестировать в материальный дизайн, бутстрап или дизайн-систему вашей собственной компании), приятно знать, что даже без модульных тестов ваше приложение не взорвется в производстве. Приятно знать, что я могу писать юнит-тесты строго по моей бизнес-логике, а не по принципу все правильно общается друг с другом. Я еще не делал этого (пытаюсь сделать на работе), но держу пари, что если бы я использовал Cypress для сквозных тестов, это было бы… скучно… потому что они всегда работали. В любом случае, мне нравится такой уровень уверенности в моем программном обеспечении.

Сначала я чувствовал себя немного расстроенным из-за того, что не использовал PureScript на Advent of Code в этом году, поскольку это была бы замечательная возможность, но это стало доказательством того, что мне не нужно знать теорию категорий, чтобы быть крутым в функционале. Программирование в сети.

Я должен указать на некоторые забавные комментарии от друзей в Интернете и вне его. В частности, о многословии Elm, особенно по сравнению с JavaScript и Python… и даже Swift.

Во-первых, Elm предназначен для интерфейса. Это означает, что у вас будет код GUI, смешанный с кодом вашего алгоритма. Многие люди, участвовавшие в Advent of Code, не создавали интерфейсы.

Во-вторых, у меня есть типы, гарантирующие отсутствие ошибок во время выполнения; JavaScript/Python этого не делают (да, я знаю о том, что в Python 3 есть выбор).

В-третьих, типы создаются только во время компиляции; У меня нет среды выполнения, такой как Java/Swift, для применения во время выполнения кода.

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

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