Ночь на башне 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на кадры, которых нет среди отправленных в журнале башни. Это и есть призраки, только теперь найденные на проводе Магистрали.
Формат провода, порядок проверок и кто считается призраком описаны в условии задачи до символа. Там же примеры, на которых ловят тесты.
Чек-лист перед сдачей
- Тесты всех прошлых этапов зелёные.
Result.tsдописан снизу,wire.tsновый модуль, остальные не тронуты. - Во всём новом коде нет ни
throw, ниtry. Ошибки живут вResult, а не в исключениях. decodeFrameна'','|||','T-1->T-2|'и'мусор'возвращаетFailure, а не падает и не выдумывает кадр.- Провод, в котором испорчены и номер, и маршрут, получает одну определённую ошибку: ту, что стоит раньше в порядке проверок.
- Текст с
|внутри доживает до сообщения целиком. - Тип
Result.genс двумя шагами с разными ошибками это объединение обеих ошибок. Проверь егоexpectTypeOf, а не на глаз. - В аудите кадр к
T-2не считается, даже если это подтверждение несуществующего кадра. Один и тот же призрак, подтверждённый дважды, попадает в ответ один раз. - Сломай разбор нарочно, например разреши
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.