Skip to content

Commit 046a93a

Browse files
author
Marat Zimnurov
committed
Теоркат, шаг 2: функтор перестал быть словом без гарантии
Объявление функтора в языке было и раньше: `функтор «Ф» из «A» в «B»` с блоком отображений разбирался и укладывался в дерево. Проверки за ним не стояло никакой — можно было написать это слово и получить ровно ничего, кроме слова. Курс, кстати, на эту ловушку и наткнулся: файл с функтором проходил `check` с ответом valid, и это легко принять за реализацию. Теперь проверяются три закона, и все три ДОКАЗЫВАЮТСЯ, а не проверяются на сетке: 1. согласование стрелки с объектами: если «ф» ведёт из «A» в «B», её образ обязан вести из образа «A» в образ «B»; 2. сохранение композиции: образ композиции обязан быть композицией образов в том же порядке — и обязан быть объявлен именно композицией, а не стрелкой с теми же концами; 3. сохранение единиц: образ тождественного морфизма объекта — тождественный морфизм его образа. Плюс однозначность отображения объектов: второй образ у того же объекта — это ошибка, а не победа последней строки. Доказательство возможно ровно потому, что морфизм здесь ОБЪЯВЛЕНИЕ, а не значение: имя, домен и кодомен известны до запуска, и «образ композиции есть композиция образов» проверяется сравнением имён — без сетки и без решателя. С функциями первого класса такой проверки не бывает: там композиция это вычисление, а равенство вычислений неразрешимо. Тот самый отказ от экспоненциалов, за который язык платит выразительностью, здесь платит обратно. ЗАКОНЫ НЕ ВКЛЮЧАЮТСЯ ПО ЖЕЛАНИЮ. В контракте стояла строка `сохраняет композицию` — отдельное разрешение на проверку. Не сделана намеренно: отображение, не сохраняющее композицию, функтором не является, и позволить написать это слово без закона значило бы продать имя вместо содержания. Граница честности закреплена тестом: имена категорий («из «Продажи» в «Биллинг»») остаются пометкой для читателя. Категория отдельной сущностью не объявляется, и утверждать, что объект принадлежит именно ей, не на чем. Тест с выдуманными категориями обязан проходить — если когда-нибудь перестанет, значит появилась проверка, и обещание в документации надо переписать. Проверено: 10 тестов законов функтора, 1434 теста языка целиком, 0 падений.
1 parent 43a89f6 commit 046a93a

2 files changed

Lines changed: 363 additions & 0 deletions

File tree

flang/src/types.mjs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export function checkTypes(program) {
113113
}
114114

115115
checkMorphisms(program, ctx)
116+
checkFunctors(program, ctx)
116117

117118
return { ok: diagnostics.length === 0, diagnostics, types: ctx.signatures }
118119
}
@@ -207,6 +208,170 @@ function checkMorphisms(program, ctx) {
207208
ctx.morphisms = стрелки
208209
}
209210

211+
/**
212+
* Функтор: отображение объектов и стрелок, обязанное сохранять устройство.
213+
*
214+
* Объявление функтора в языке было и раньше — `функтор «Ф» из «A» в «B»` с
215+
* блоком `объект … отображается в …` и `морфизм … отображается в морфизм …`.
216+
* Не было проверки: слово «функтор» стояло, а гарантии за ним не стояло
217+
* никакой. Здесь появляется гарантия.
218+
*
219+
* ЗАКОНЫ НЕ ВКЛЮЧАЮТСЯ ПО ЖЕЛАНИЮ. В контракте была строка `сохраняет
220+
* композицию` — отдельное разрешение на проверку. Она не сделана намеренно:
221+
* отображение, не сохраняющее композицию, функтором не является, и дать
222+
* написать это слово без закона значило бы продать имя вместо содержания.
223+
*
224+
* ЧТО ИМЕННО ДОКАЗЫВАЕТСЯ, а не проверяется на примерах. Всё нижеследующее —
225+
* утверждения обо всех входах, и берутся они из одних объявлений, без сетки и
226+
* без решателя. Это возможно ровно потому, что морфизм здесь объявление, а не
227+
* значение: у функций первого класса такой проверки не бывает.
228+
*
229+
* 1. согласование стрелки с объектами: если «ф» ведёт из «A» в «B», то её
230+
* образ обязан вести из образа «A» в образ «B». Ровно та же стыковка, что
231+
* у композиции, только через отображение;
232+
* 2. сохранение композиции: образ композиции обязан быть композицией образов
233+
* в том же порядке;
234+
* 3. сохранение единиц: образ тождественного морфизма объекта обязан быть
235+
* тождественным морфизмом его образа;
236+
* 4. отображение объектов однозначно — иначе это не функция.
237+
*
238+
* Чего проверка НЕ делает и делать не может: имена категорий («из «Продажи» в
239+
* «Биллинг»») остаются пометкой для читателя. Категория в языке не объявляется
240+
* отдельной сущностью, и утверждать, что объект принадлежит именно этой
241+
* категории, не на чем.
242+
*/
243+
function checkFunctors(program, ctx) {
244+
const наследие = Array.isArray(program?.legacy) ? program.legacy : []
245+
const функторы = наследие.filter((узел) => узел?.construct === "functorFile")
246+
if (функторы.length === 0) return
247+
248+
const стрелки = ctx.morphisms instanceof Map ? ctx.morphisms : new Map()
249+
const морфизмы = Array.isArray(program?.morphisms) ? program.morphisms : []
250+
/* Композиции берутся из исходных узлов, а не из `стрелки`: там от композиции
251+
остались только домен и кодомен, а закону нужны имена сомножителей. */
252+
const композиции = new Map()
253+
for (const узел of морфизмы) {
254+
if (узел?.kind === "composition") композиции.set(узел.name, узел)
255+
}
256+
const единицы = new Map()
257+
for (const узел of морфизмы) {
258+
if (узел?.kind === "morphism" && узел.identity === true) единицы.set(узел.name, узел.domain)
259+
}
260+
261+
for (const { value: функтор, span } of функторы) {
262+
const узел = { span }
263+
const образОбъекта = new Map()
264+
for (const пара of функтор.objects ?? []) {
265+
if (образОбъекта.has(пара.from)) {
266+
ctx.report(
267+
"FLANG_FUNCTOR_OBJECT_TWICE",
268+
`функтор «${функтор.name}»: объект «${пара.from}» отображается дважды — ` +
269+
`в «${образОбъекта.get(пара.from)}» и в «${пара.to}»; отображение объектов обязано быть однозначным`,
270+
узел,
271+
)
272+
continue
273+
}
274+
образОбъекта.set(пара.from, пара.to)
275+
}
276+
277+
const образМорфизма = new Map()
278+
for (const пара of функтор.morphisms ?? []) образМорфизма.set(пара.from, пара.to)
279+
280+
/* Закон 1: стрелка согласована с объектами. */
281+
for (const пара of функтор.morphisms ?? []) {
282+
const исходная = стрелки.get(пара.from)
283+
const образ = стрелки.get(пара.to)
284+
if (исходная === undefined) {
285+
ctx.report(
286+
"FLANG_UNKNOWN_NAME",
287+
`функтор «${функтор.name}» отображает «${пара.from}», но такого морфизма нет`,
288+
узел,
289+
)
290+
continue
291+
}
292+
if (образ === undefined) {
293+
ctx.report(
294+
"FLANG_UNKNOWN_NAME",
295+
`функтор «${функтор.name}» отображает «${пара.from}» в «${пара.to}», но морфизма «${пара.to}» нет`,
296+
узел,
297+
)
298+
continue
299+
}
300+
for (const [роль, конец] of [["домен", "domain"], ["кодомен", "codomain"]]) {
301+
const объект = исходная[конец]
302+
if (!образОбъекта.has(объект)) {
303+
ctx.report(
304+
"FLANG_FUNCTOR_OBJECT_MISSING",
305+
`функтор «${функтор.name}» отображает морфизм «${пара.from}», но не отображает его ${роль} ` +
306+
${объект}»: без образа объекта образ стрелки не с чем согласовать`,
307+
узел,
308+
)
309+
continue
310+
}
311+
const ожидается = образОбъекта.get(объект)
312+
if (образ[конец] !== ожидается) {
313+
ctx.report(
314+
"FLANG_FUNCTOR_ARROW_MISMATCH",
315+
`функтор «${функтор.name}»: «${пара.from}» имеет ${роль} «${объект}», его образ — «${ожидается}», ` +
316+
`но «${пара.to}» имеет ${роль} «${образ[конец]}»`,
317+
узел,
318+
)
319+
}
320+
}
321+
}
322+
323+
/* Закон 2: образ композиции — композиция образов, в том же порядке. */
324+
for (const [имя, композиция] of композиции) {
325+
if (!образМорфизма.has(имя)) continue
326+
const образЛевой = образМорфизма.get(композиция.left)
327+
const образПравой = образМорфизма.get(композиция.right)
328+
if (образЛевой === undefined || образПравой === undefined) {
329+
const без = образЛевой === undefined ? композиция.left : композиция.right
330+
ctx.report(
331+
"FLANG_FUNCTOR_COMPOSITION",
332+
`функтор «${функтор.name}» отображает композицию «${имя}», но не отображает «${без}», ` +
333+
`из которой она собрана: закон сохранения композиции проверить не на чем`,
334+
узел,
335+
)
336+
continue
337+
}
338+
const образКомпозиции = композиции.get(образМорфизма.get(имя))
339+
if (образКомпозиции === undefined) {
340+
ctx.report(
341+
"FLANG_FUNCTOR_COMPOSITION",
342+
`функтор «${функтор.name}»: образ композиции «${имя}» — «${образМорфизма.get(имя)}» — сам композицией не объявлен, ` +
343+
`а обязан быть «${образЛевой}» после «${образПравой}»`,
344+
узел,
345+
)
346+
continue
347+
}
348+
if (образКомпозиции.left !== образЛевой || образКомпозиции.right !== образПравой) {
349+
ctx.report(
350+
"FLANG_FUNCTOR_COMPOSITION",
351+
`функтор «${функтор.name}» не сохраняет композицию: образ «${имя}» обязан быть ` +
352+
${образЛевой}» после «${образПравой}», а объявлен как «${образКомпозиции.left}» после «${образКомпозиции.right}»`,
353+
узел,
354+
)
355+
}
356+
}
357+
358+
/* Закон 3: образ единицы — единица образа. */
359+
for (const [имя, объект] of единицы) {
360+
if (!образМорфизма.has(имя)) continue
361+
if (!образОбъекта.has(объект)) continue
362+
const ожидается = `единица ${образОбъекта.get(объект)}`
363+
if (образМорфизма.get(имя) !== ожидается) {
364+
ctx.report(
365+
"FLANG_FUNCTOR_IDENTITY",
366+
`функтор «${функтор.name}» не сохраняет единицы: образ «${имя}» обязан быть «${ожидается}», ` +
367+
`а объявлен «${образМорфизма.get(имя)}»`,
368+
узел,
369+
)
370+
}
371+
}
372+
}
373+
}
374+
210375
/* ------------------------------------------------------------------ */
211376
/* Объявления */
212377
/* ------------------------------------------------------------------ */

0 commit comments

Comments
 (0)