Раздел 31 · Janet на практике

Ветвления и match

junior~30 мин

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

Ветвления и 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 с набором глаголов.

домашка

Домашка