Время прочтения: 7 мин.

Elm – это функциональный язык программирования, который компилируется во всеми любимый javascript. Он обладает всеми плюсами и минусами этого семейства языков. Из самых важных особенностей стоит отметить, что язык является компилируемым. А также свойства языка принуждают вас сразу обдумать архитектуру вашего приложения: расписать, что попадёт в конкретную функцию, что из неё выйдет, и куда пойдёт дальше.

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

Начну сразу с архитектуры приложений на Elm, которая показана на картинке ниже:

Elm генерирует HTML для отображения на экране, а затем приложение отправляет обратно сообщения (Msg) о каком-либо событии (например, нажатие кнопки).

Любое SPA (single page application) на Elm состоит из следующих элементов:

Init – начальное состояние модели.

Model — текущее состояние проекта.

View — отображение model.

Update — обновление состояния проекта на основе сообщений (Msg).

Что я буду делать?

Для того что бы разобраться как работает Elm, было реализовано великое и могучее SPA под названием To-Do List (список дел) с возможностью записи дел и проведения некоторых операций над ними. Для демонстрации работы с запросами было добавлено соединение с БД (базой данных).

Наше SPA To-Do List будет обладать следующими возможностями:

  • создание элемента To-Do List’а;
  • редактирование (изменение названия и статуса выполнения задачи);
  • удаление;
  • сохранение в БД списка дел.

А реализовывать всё это мы будем с помощью следующих технологий:

  1. Elm – виновник торжества.
  2. Json-server и db.json в роли базы данных (БД).
  3. Bootstrap – будет отвечать за красоту. 

Начинаем с начала

Для того что бы использовать Elm, как бы это странно не звучало, необходимо установить Elm. Лучше скачивать с официального сайта (elm-lang.org). Так же на нём находится самая актуальная документация языка, которая по своей структуре напоминает обучающий курс. Можно скачать и с pypi, но пакет там не обновлялся с конца 2018 года.

После того как Elm установился, необходимо перейти в директорию проекта и запустить его с помощью команды:

elm reactor

Проект запустится на localhost:8000 и всё, достаточно изредка нажимать ctrl+s для сохранения и проект будет автоматически обновляться, не нужен даже LiveServer.

Архитектура приложения

Приложение состоит из 3-х модулей, каждый из которых вынесен в отдельный файл структура проекта показана на изображении ниже.

  1. Main – основной модуль приложения, в котором происходит его инициализация, добавление в проект теги html, body и всех статичных элементов, а также вызов других модулей. Код данного модуля находится в файле с логичным названием Main.elm.
Main : Program Value Model Msg
Main = 
    Browser.document
        { init = =init,
        , view = view
        , update = update
        , subscriptions = subscriptions
        }

type Msg = NoOp | TodosMsg TodosMsg.Msg

type alias Model = {todos : List Todo, todoEditView : TodoEditView}

init : flags -> ( Model, Cmd Msg )
init fs = 
    let model = Model [] None
        cmds = Cmd.batch[ Cmd.map TodosMsg Todos.Models.fetchAll ]
    in ( model, cmds)

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = 
    case msg of
        let
            ( newTodoEditView, newTodos, cmd ) = Todos.Models.update subMsg mod-el.todoEditView model.todos
            newModel = { model | todoEditView = newTodoEditView, todos = newTodos }
        in ( newModel, Cmd.map TodosMsg cmd )
        
view : Model -> Document Msg
view model = 
    { title = "Туду"
    , body = 
        [ div [] 
            [
            Html.map TodosMsg <| Todos.Models.viewEdit model.todoEditView
            , br [] []
            , Html.map TodosMsg <| Todos.Models.viewList model.todos
            ]   
        ]
    }

2. Models – модуль приложения, который отвечает за список дел. Из-за того, что пример небольшой, всё хранилось в одном месте. Но при более серьёзном проекте, хорошим тоном будет разделить код на разные модули. Здесь лежит таблица, которая хранит в себе дела, а также осуществлялись все действия с ними. Ниже приведена часть кода из данного модуля, который лежит в директории Todos и называется Models.elm.

-- Корпус таблицы
viewList : List Todo -> Html Msg
viewList todos =
    Table.table{ options = [ Table.striped ]
    , thead = Table.thead []
        [ Table.tr []
            [ Table.th [] [ text "Название" ]
            , Table.th [] [ text "Готово" ]
            , Table.th [] [
                     div [][ text "Действия" ]
                    , div[][ delCompl ]
                    ]
            ]
        ]
    , tbody = Table.tbody [] ( List.map todoRow todos )
    }

-- Строки таблицы
todoRow : Todo -> Table.Row Msg
todoRow t = 
    let 
        { id, title, completed } = table
        ( completedText, buttonText, buttonMsg ) = 
            if completed then ("Да", "Невыполненно", Revert )
            else ("Нет", "Выполненно", Complete)
        in
        Table.tr []
            [ Table.td [] [ text title ]
            , Table.td [] [ text completedText ]
            , Table.td [] [ 
                Button [ onClick <| buttonMsg t ][ text buttonText ]
                , Button [ onClick <| ShowEditView <| Editing t ][ text "Редакти-ровать" ]
                , Button [ onClick <| Delete t ][ text "Удалить" ]
            ]
            
-- Функция удаления
delCompl : Html Msg
delCompl = Button[ onClick DeleteCompleted ][ text "Удалить выполненные" ]

3. Utils – модуль связи с БД. Здесь находится всё, что связано с данными, а именно: кодирование и декодирование json’а и запросы, для выполнения которых использовался пакет Http.request. Пример запроса показан ниже. Код вынесен из модуля Utils.elm.

--запрос для удаления
delete : a -> String -> Platform.Task Http.Error a
delete a url =
    let decoder = Json.Decode.succeed a
        request = 
            Http.request
                {method = "DELETE"
                , headers = []
                , url = url
                , body = Http.emptyBody
                , expect = Http.expectJsopn decoder
                , timeout = Maybe.Nothing
                , withCredentials = False
                }
            in Http.toTask request

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

Для тестового проекта, который никто не увидит – сойдёт. Но я делаю его для души, поэтому просто необходимо добавить немного красоты. И Elm в этом поможет, с помощью тесной интеграции с Bootstrap. Подключаю его следующей строкой в main:

import Bootstrap.CDN as CDN 

А также импортирую bootstrap классы добавляя элиасы для простоты вызова:

import Bootstrap.Button as Button  # и т.д. по необходимости

Благодаря bootstrap’у и небольшой доработке кода получается следующая картина:

При изменении названия, кнопка «Новая задача» заменяется на поля для редактирования наименования дела.

База данных

Для подъёма базы данных я использовал json-server, который был запущен на 4000 порту. Всего БД имеет 3 поля для каждого дела, а именно:

title <str> — название дела;

completed <boolean> — готовность дела;

id <int> — идентификатор дела.

Команда для запуска следующая:

Json-server db.json -p 4000

В итоге база данных выглядит следующим образом:

Что касается ответа на вопрос:

«Elm — забава или серьёзный инструмент?», он очень прост — всё зависит от вашего проекта. От себя скажу, что изначально программирование на Elm было похоже на поход в магазин на руках. Из-за того, что всё было непривычно, Elm требовал на разработку часы, когда с javascript на то же самое хватило бы и нескольких минут. Данное неудобство связано скорее с изначальным обучением программированию в императивном стиле и обычной привычкой.

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

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

Полный код приложения доступен по ссылке.