Elm
Экспресс-тур по Elm: чисто функциональный язык для веба без runtime-ошибок. Типы, списки, записи, Maybe, архитектура Model-Update-View.
Elm — чисто функциональный язык для веб-фронтенда, который компилируется в JavaScript и славится отсутствием runtime-ошибок. Строгая статическая типизация, иммутабельность и архитектура Model-Update-View. Весь язык — на одной странице через комментарии в коде.
Комментарии и вывод
-- Однострочный комментарий начинается с двух дефисов.
{- Многострочный комментарий
может занимать несколько строк.
{- И даже влагаться внутрь другого. -}
-}
-- Базовые значения и их литералы:
42 -- целое число (Int)
3.14 -- число с плавающей точкой (Float)
True -- логическое (Bool): True или False
'a' -- символ (Char) — в одинарных кавычках
"Привет" -- строка (String) — в двойных
-- Арифметика. Деление: / для Float, // для целочисленного.
2 + 3 * 4 -- 14
10 / 3 -- 3.3333 (Float)
10 // 3 -- 3 (целочисленное деление)
2 ^ 10 -- 1024 (возведение в степень)
-- Конкатенация строк оператором ++
"Code" ++ "chick" -- "Codechick"
Функции
Функции — основа языка. Аргументы пишутся через пробел, без скобок.
-- Определение функции: имя аргументы = тело
add x y =
x + y
-- Применение (аппликация): аргументы через пробел
add 2 3 -- 5
-- Лямбда (анонимная функция): \аргументы -> тело
double =
\n -> n * 2
double 21 -- 42
-- Частичное применение: все функции каррированы.
-- Передаём меньше аргументов — получаем новую функцию.
addTen =
add 10 -- функция, ждущая один аргумент
addTen 5 -- 15
-- Оператор |> (pipe): передаёт значение слева как
-- последний аргумент функции справа. Читается слева направо.
5
|> add 10 -- 15
|> double -- 30
Типы и аннотации
Elm строго типизирован. Типы выводятся автоматически, но аннотации принято писать явно.
-- Аннотация типа идёт над определением, через ::
-- Стрелка -> разделяет аргументы и результат.
add : Int -> Int -> Int
add x y =
x + y
-- Базовые типы:
age : Int -- целое число
age = 25
pi : Float -- число с точкой
pi = 3.14159
isReady : Bool -- логическое
isReady = True
name : String -- строка
name = "Elm"
grade : Char -- символ
grade = 'A'
-- Важно: Int и Float не смешиваются неявно.
-- toFloat и round/floor преобразуют явно.
toFloat 10 / 3 -- 3.3333
Списки
Список (List) — упорядоченная коллекция элементов одного типа.
numbers : List Int
numbers = [ 1, 2, 3, 4, 5 ]
empty = [] -- пустой список
-- Добавление в начало оператором :: (cons)
0 :: numbers -- [0, 1, 2, 3, 4, 5]
-- Объединение списков через ++
[ 1, 2 ] ++ [ 3, 4 ] -- [1, 2, 3, 4]
-- map: применить функцию к каждому элементу
List.map (\n -> n * 2) numbers -- [2, 4, 6, 8, 10]
-- filter: оставить элементы, удовлетворяющие условию
List.filter (\n -> n > 2) numbers -- [3, 4, 5]
-- foldl: свёртка слева направо (аккумулятор)
List.foldl (+) 0 numbers -- 15 (сумма)
-- Другие полезные функции:
List.length numbers -- 5
List.reverse numbers -- [5, 4, 3, 2, 1]
List.sum numbers -- 15
Кортежи и записи
Кортеж (tuple) группирует фиксированное число значений разных типов. Запись (record) — именованные поля.
-- Кортеж: значения в круглых скобках через запятую
point : ( Int, Int )
point = ( 3, 4 )
-- Извлечение для пар — Tuple.first / Tuple.second
Tuple.first point -- 3
Tuple.second point -- 4
-- Запись: поля с именами в фигурных скобках
user : { name : String, age : Int }
user =
{ name = "Alice", age = 30 }
-- Доступ к полю через точку
user.name -- "Alice"
-- Иммутабельное обновление: { запись | поле = новое }
-- Создаёт НОВУЮ запись, старая не меняется.
olderUser =
{ user | age = 31 }
olderUser.age -- 31
user.age -- 30 (осталась прежней)
Условия
-- if/then/else — всегда выражение, ветка else обязательна.
-- Обе ветки должны возвращать значение одного типа.
describe : Int -> String
describe n =
if n > 0 then
"положительное"
else if n < 0 then
"отрицательное"
else
"ноль"
-- case/of — сопоставление с образцом. Мощнее if.
russian : Int -> String
russian n =
case n of
1 -> "один"
2 -> "два"
3 -> "три"
_ -> "много" -- _ ловит всё остальное
Пользовательские типы
Ключевое слово type создаёт свои типы с несколькими вариантами (union types).
-- Перечисление вариантов через |
type Color
= Red
| Green
| Blue
-- Варианты могут нести данные:
type Shape
= Circle Float -- радиус
| Rectangle Float Float -- ширина и высота
-- Maybe — встроенный тип для "может быть пусто".
-- Заменяет null: type Maybe a = Just a | Nothing
findAge : String -> Maybe Int
findAge name =
if name == "Alice" then
Just 30
else
Nothing
-- Result — для операций, которые могут упасть.
-- type Result error value = Ok value | Err error
safeDivide : Float -> Float -> Result String Float
safeDivide a b =
if b == 0 then
Err "деление на ноль"
else
Ok (a / b)
Сопоставление с образцом
case разбирает пользовательские типы. Компилятор требует обработать ВСЕ варианты.
-- Разбор union-типа с извлечением данных
area : Shape -> Float
area shape =
case shape of
Circle r ->
3.14159 * r * r
Rectangle w h ->
w * h
-- Разбор Maybe — безопасная работа с отсутствием
greet : Maybe Int -> String
greet maybeAge =
case maybeAge of
Just age ->
"Возраст: " ++ String.fromInt age
Nothing ->
"Возраст неизвестен"
-- Разбор списка по голове и хвосту (x :: rest)
firstOrZero : List Int -> Int
firstOrZero list =
case list of
[] ->
0
x :: _ ->
x
Функции высшего порядка и композиция
Функции можно передавать в другие функции и склеивать в конвейеры.
-- Функция принимает другую функцию аргументом
applyTwice : (a -> a) -> a -> a
applyTwice f x =
f (f x)
applyTwice (\n -> n + 3) 0 -- 6
-- Композиция слева направо: >>
-- (f >> g) x равно g (f x)
incThenDouble =
(\n -> n + 1) >> (\n -> n * 2)
incThenDouble 5 -- 12
-- Композиция справа налево: <<
-- (f << g) x равно f (g x)
doubleThenInc =
(\n -> n + 1) << (\n -> n * 2)
doubleThenInc 5 -- 11
Модули
-- Объявление модуля и что он экспортирует.
-- exposing (..) открывает всё; лучше перечислять явно.
module Math exposing (add, square)
-- Импорт модуля целиком (доступ через List.map)
import List
-- Импорт с алиасом (Html.Attributes → Attr)
import Html.Attributes as Attr
-- Импорт конкретных имён в текущее пространство
import Html exposing (div, text)
-- Импорт типа со всеми его вариантами
import Maybe exposing (Maybe(..))
Архитектура Elm (Model-Update-View)
Любое Elm-приложение строится на трёх частях: состояние (Model), обновление (update) и отображение (view). Это и есть "The Elm Architecture".
-- 1. MODEL — всё состояние приложения
type alias Model =
{ count : Int }
init : Model
init =
{ count = 0 }
-- 2. MESSAGES — что может произойти
type Msg
= Increment
| Decrement
-- 3. UPDATE — как меняется Model в ответ на Msg
-- Возвращает НОВЫЙ Model (иммутабельно).
update : Msg -> Model -> Model
update msg model =
case msg of
Increment ->
{ model | count = model.count + 1 }
Decrement ->
{ model | count = model.count - 1 }
-- 4. VIEW — как Model превращается в HTML.
-- Клики порождают Msg, которые идут в update.
view : Model -> Html Msg
view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, text (String.fromInt model.count)
, button [ onClick Increment ] [ text "+" ]
]
Особенности Elm
-- НЕТ null И undefined.
-- Отсутствие значения выражается типом Maybe,
-- поэтому "невозможно прочитать свойство null" не бывает.
safe : Maybe Int
safe = Nothing
-- НЕТ исключений (exceptions) во время выполнения.
-- Ошибки выражаются типом Result и обрабатываются явно.
parse : String -> Result String Int
parse s =
String.toInt s
|> Result.fromMaybe "не число"
-- ИММУТАБЕЛЬНОСТЬ: данные нельзя изменить на месте.
-- Любое "изменение" создаёт новое значение.
original = [ 1, 2, 3 ]
changed = 0 :: original -- [0, 1, 2, 3]
-- original всё ещё [1, 2, 3]
-- ЧИСТОТА: функции без побочных эффектов.
-- Один вход — всегда один выход. Все эффекты
-- (HTTP, время) управляются через Cmd и Sub.