Ветвления и match
открытый урокЭтот раздел читается без входа. Войди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти
Ветвления и match
Лесенка от
ifдо сопоставления с образцом. Внизу простой выбор ветки, наверху разбор структуры данных со связыванием имён. И одна ловушка вmatch, которая стоит отдельного разговора.
Истинность, ещё раз
Начнём с кода, который выглядит правильным и врёт:
Автор ждал, что ноль окажется ложью, как в JavaScript. Но в Janet ложны только nil и false. Ноль, пустая строка и пустой массив это истина:
Поэтому (if (length xs) ...) не проверяет непустоту: длина 0 истинна. Пиши (if (empty? xs) ...). От этого правила зависит всё в этом уроке.
if, when, unless
(if условие тогда иначе) # ветка иначе необязательна
(when условие тело...) # только когда истинно
(unless условие тело...) # только когда ложно
if берёт ровно одно выражение на ветку. Нужно несколько, бери do, либо when и unless, которые заворачивают тело в do сами. Мы видели их раскрытие в уроке про окружения:
if-let и when-let
Следующая ступень после if: if-let и when-let связывают имя и проверяют его одним движением. Если значение nil или false, if-let уходит в ветку “иначе”, а when-let возвращает nil:
Обычный if заставил бы сначала связать значение через def, потом проверить: два шага вместо одного. Идиома для сценария “достань и обработай, если нашлось”.
cond
cond это цепочка “условие, значение”. Нечётный последний элемент становится веткой “иначе”:
Условия проверяются по порядку, и каждое опирается на провал предыдущих: до (< code 400) доходят только коды от 300 и выше.
case
case сравнивает одно значение с константами через =. Он короче cond, когда все проверки это равенство:
Провала между ветками, как в C, здесь нет.
case без ветки “иначе” возвращает nil.
Никакой ошибки не будет. Если отсутствие совпадения это ошибка в твоей логике, добавляй явную ветку с error.
match
match это сопоставление с образцом: он не только выбирает ветку, но и разбирает структуру данных, связывая переменные.
Литералы работают как в case, а одиночный символ связывается с чем угодно:
Подчёркивание _ это общепринятое имя для “что угодно, значение не нужно”.
Главное это деструктуризация:
В образце структуры перечисляются только интересующие ключи, остальные игнорируются.
Лесенка на одном примере
Соберём все ступени на одной задаче. Событие лога это кортеж вида [:err 500 "упала база"] или [:info "стартуем"], нужна человекочитаемая строка.
На cond громоздко: сначала проверяем первый элемент, потом руками достаём остальные по индексам:
(defn describe-event [event]
(cond
(= (first event) :err)
(string "ошибка " (event 1) ": " (event 2))
(= (first event) :info)
(string "инфо: " (event 1))
"непонятное событие"))
case здесь не работает никак: он сравнивает значение целиком, а нужна проверка формы “первый элемент такой, остальное разобрать”.
match делает проверку и разбор одним образцом:
Это и есть смысл лесенки: чем выше ступень, тем меньше конструкция выбирает ветку и тем больше разбирает данные.
Стражи это дополнительное условие в скобках после образца. Синтаксис легко прочитать неправильно: (x (> x 3)) выглядит как вызов функции. На деле первый элемент скобок связывает имя, а всё остальное это проверки, каждая из которых должна оказаться истинной:
Стенд ниже сопоставляет по-настоящему: правь значение и образцы, смотри, какая ветка сработала и что связалось.
Ловушка: длина образца это минимум, а не точное совпадение
Образец кортежа совпадает, если сходятся его первые элементы. Лишние элементы данных игнорируются:
Если нужна точная длина, проверяй её стражем:
(match data
(p (= 2 (length p))) :exactly-two
_ :none)
Это регулярный источник ошибок: образец [command arg] тихо примет форму с тремя элементами и молча отбросит третий. Открой пресет “ловушка: длина это минимум” в стенде выше и убедись сам.
Если ты работал с сопоставлением с образцом в Gleam или Haskell, здесь важно перестроиться: там компилятор требует точного разбора и ругается на неполноту, в Janet проверка целиком на тебе.
Лесенка целиком
| Конструкция | Что умеет | Где упирается |
|---|---|---|
if, when, unless | одна проверка | цепочка условий превращается во вложенность |
if-let, when-let | проверка плюс связывание имени | одно условие, без разбора структуры |
cond | цепочка любых условий | данные достаются руками, по индексам |
case | сравнение с константами | только равенство целиком |
match | проверка формы плюс разбор со связыванием | полноту веток не проверяет никто, кроме тебя |
Выбирай самую низкую ступень, которой хватает: чем ниже, тем проще читать.
Что запомнить
- Ложны только
nilиfalse.(if (length xs) ...)не проверяет непустоту. ifберёт одно выражение на ветку,whenиunlessзаворачивают тело вdoсами.if-letиwhen-letсвязывают и проверяют одним движением.condдля цепочки условий,caseдля сравнения с константами, без провала между ветками.caseбез ветки “иначе” молча возвращаетnil.matchразбирает структуру и связывает имена,_означает “что угодно”.- В образце структуры перечисляют только нужные ключи.
- Страж это условие в скобках после имени.
- Длина образца кортежа это минимум. Лишние элементы данных отбрасываются молча.
- Лесенка: внизу выбор ветки, наверху разбор данных. Бери самую низкую ступень, которой хватает.
Упражнения
Дальше
Ветвления закрыты. Дальше циклы, и здесь Janet поступает нетипично: вместо десятка конструкций один универсальный loop с набором глаголов.
домашка