Раздел 46 · Effect: Тихая Академия

Босс: котёл Матушки

middle-senior~80 мин

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

Ночь на башне T-1. Туман над рекой, ставни щёлкают в темноте, Сундук стоит у лестницы. Матушка села на табурет у печки с вязанием и ни разу не подняла глаз. Рыдкулл ушёл, но дверь оставил открытой.

За двенадцать этапов котёл научился многому: журнал башни и сводка смены, план повтора, доска башен, условия из маленьких функций, pipe и flow, двойные функции, судьба кадра, коробка Option, дорога Result, все ошибки сразу, журнал с заметками по законам и рецепт на генераторе. Но провод Магистрали он ещё не читал. Для этого не хватает двух вещей: рецепта для дороги в два пути и самого разбора кадра.

Что проверяет Матушка

Проверяется всё, что котёл умеет с первого этапа: тесты накопительные. Сверху три новые вещи.

  • Рецепт для Result. Result.open и Result.gen устроены как их родня для Option из урока 28, но ошибка не теряется: первая неудача останавливает рецепт и становится его ответом. Главное в типе. Если один шаг может упасть с Steps, а другой с Other, у рецепта ошибка ровно Steps | Other, и выводит её компилятор, а не ты. Как вынуть ошибки из объединения результатов, ты уже знаешь: так же, как ErrorOf доставал ошибку из задачи в уроке 11, условным типом с infer, который распределяется по объединению.
  • Разбор кадра. decodeFrame читает провод того же вида, что протокол из урока 16: маршрут|номер|тег|данные. Но теперь это рецепт на Result.gen, и у каждой поломки свой тег: Garbled, BadRoute, UnknownTag и новый BadPayload, когда данные не подходят тегу. На любую строку разбор отвечает Result. Тесты проверят это не на примерах, а на сотнях случайных строк.
  • Аудит ночи. auditNight разбирает все кадры ночи. Если битые есть, возвращает их все, по порядку, как validateAll из урока 26. Если битых нет, возвращает подтверждения к T-1 на кадры, которых нет среди отправленных в журнале башни. Это и есть призраки, только теперь найденные на проводе Магистрали.

Формат провода, порядок проверок и кто считается призраком описаны в условии задачи до символа. Там же примеры, на которых ловят тесты.

Чек-лист перед сдачей

  1. Тесты всех прошлых этапов зелёные. Result.ts дописан снизу, wire.ts новый модуль, остальные не тронуты.
  2. Во всём новом коде нет ни throw, ни try. Ошибки живут в Result, а не в исключениях.
  3. decodeFrame на '', '|||', 'T-1->T-2|' и 'мусор' возвращает Failure, а не падает и не выдумывает кадр.
  4. Провод, в котором испорчены и номер, и маршрут, получает одну определённую ошибку: ту, что стоит раньше в порядке проверок.
  5. Текст с | внутри доживает до сообщения целиком.
  6. Тип Result.gen с двумя шагами с разными ошибками это объединение обеих ошибок. Проверь его expectTypeOf, а не на глаз.
  7. В аудите кадр к T-2 не считается, даже если это подтверждение несуществующего кадра. Один и тот же призрак, подтверждённый дважды, попадает в ответ один раз.
  8. Сломай разбор нарочно, например разреши Ping с данными, и убедись, что тест покраснел. Потом верни.

Если шаг Т ругается на yield в рецепте, проверь звёздочку: этот «Ук» ты уже видел в прошлом уроке.

Котёл, этап 13

Последний заказ раздела: котёл читает провод Магистрали целиком и сверяет его с журналом башни. После него можно будет сказать, врёт ли Магистраль, не поверив никому на слово, ни ей, ни своим глазам.

Ринцвальд

Пока ты пишешь, Ринцвальд заглядывает в дверь и кладёт на стол свой листок.

import type { Frame, Message } from './wire';

const decodeFrame = (wire: string): Frame | undefined => {
  try {
    const [route = '', id = '', tag = '', ...rest] = wire.split('|');
    const [from = '', to = ''] = route.split('->');
    const payload = rest.join('|');
    const message = (
      tag === 'Ack' ? { _tag: 'Ack', of: Number(payload) } : { _tag: tag, body: payload }
    ) as Message;
    return { from, to, id: Number(id), message };
  } catch {
    return undefined;
  }
};

console.log(decodeFrame('T-7->T-1|41|Ack|40'));
console.log(decodeFrame('T-7->T-1|42|Ack|сорок'));
console.log(decodeFrame('мусор'));
{ from: 'T-7', to: 'T-1', id: 41, message: { _tag: 'Ack', of: 40 } }
{ from: 'T-7', to: 'T-1', id: 42, message: { _tag: 'Ack', of: NaN } }
{ from: 'мусор', to: '', id: 0, message: { _tag: '', body: '' } }

Он прав: не падает. Здесь и падать нечему, try ни разу не сработал. Второй кадр подтверждает кадр номер NaN, и аудит честно запишет его в призраки: в журнале такого номера нет. Третий провод стал нулевым кадром от башни «мусор» с сообщением без тега. as Message заставил компилятор поверить, что так и надо.

Матушка смотрит на листок, не откладывая вязания.

Сигнатура (wire: string) => Frame | undefined обещает, что кадр либо есть, либо нет. Почему его нет, она не говорит, и аудит не может назвать битые кадры поимённо. Твоя сигнатура перечисляет, как кадр может не разобраться, и ни одна строка провода не превращается в кадр, если не прошла все проверки.

Практика

Итог

К рассвету аудит готов. Ни одного битого кадра: Магистраль пишет по правилам, к форме не придраться. А подтверждений, которые не сходятся с журналом башни, два. Магистраль подтвердила T-1 кадры, которых T-1 никогда не отправляла. Её зелёные отчёты собраны из ответов на то, чего не было.

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

Ранга за этот раздел нет: Матушка рангов не раздаёт. К обеду её телега уже поднимается по дороге в горы. Сундук провожает её до ворот, а у тебя на столе остаётся котёл: журнал башни и своя мини-библиотека, в которой ни одна функция ничего не делает, а только считает.

Дальше

Вечером Рыдкулл читает отчёт об аудите у себя в кабинете и долго молчит. Магистраль подтверждает то, чего не было, и делает это давно. А Пондер берёт со стены лампу.

Следующий раздел курса это EF3 «Свой Гекс». Пондер открывает подвал Академии, и вы вдвоём собираете маленький рантайм эффектов, чтобы Effect потом не казался магией. Начинается он с урока 30.