Team Decisions¶
Лог архитектурных и организационных решений команды.
Шаблон записи¶
- Дата:
- Участники:
- Вопрос:
- Решение:
- Причина:
- Дата: 01.05.2026
- Участники: Женя (Участник 3)
- Вопрос: Как организовать трекинг задач и этапов на GitHub?
- Решение: Создать Milestones под каждый этап из плана. Каждый Issue привязывается к соответствующему Milestone. PR закрывает Issue через "Closes #номер".
- Причина: Milestones дают команде и преподавателю наглядный прогресс по этапам. Названия Issue в формате "тип: описание" обеспечивают читаемую историю проекта.
- Дата: 01.05.2026
- Участники: Женя (Участник 3)
- Вопрос: Как декомпозировать работу команды на конкретные задачи?
- Решение: Составлен список предполагаемых Issue для каждого Milestone. Каждый участник создаёт Issue под свою зону ответственности по мере начала работы над этапом.
- Причина: Явная декомпозиция помогает избежать параллельной работы над одним и тем же и даёт преподавателю прозрачную картину вклада каждого участника.
- Дата: 02.05.2026
- Участники: Максим (Участник 1), Сергей (Участник 2), Женя (Участник 3)
- Вопрос: Какой синтаксис языка выбрать, чтобы он был достаточно самобытным, но оставался реалистичным для ручного парсера на F#?
- Решение: В качестве базового направления выбран Lisp-like синтаксис
с современным сахаром для лямбд через
=>. Базовый вариант:
Поддерживаемые или желательные расширения:
- многострочные лямбды без изменения семантики;
- несколько аргументов через ((x y) => ...);
- let x = ... вместо полностью позиционного (let x value body);
- улучшенные сообщения об ошибках;
- строковые литералы как низкоприоритетное расширение;
- pipeline-оператор |> как возможное stretch goal, если останется время.
ML-подобный синтаксис с инфиксными операторами пока не выбирается как базовый,
потому что он заметно усложняет парсер: нужно учитывать приоритеты операторов,
ассоциативность и форму let rec.
- Причина: Чистый Lisp проще всего парсить, но выглядит слишком учебно.
Вариант с
=>иlet x = ...сохраняет простоту S-выражений, но делает язык визуально более отличимым. Pipeline и строки можно добавить позже, если основные части языка уже работают.
- Дата: 02.05.2026
- Участники: Женя (Участник 3), Максим (Участник 1), Сергей (Участник 2)
- Вопрос: Как избежать конфликтов в Tests.fs при параллельной разработке?
- Решение: Разделить Tests.fs на отдельные файлы по зонам ответственности: SmokeTests.fs, EvaluatorTests.fs, BuiltinTests.fs, ExampleTests.fs. Каждый участник пишет только в свой файл.
- Причина: При параллельной работе над одним файлом тестов неизбежны merge-конфликты. Разделение по файлам устраняет проблему физически — так же как разделение AI usage логов по участникам.
- Дата: 02.05.2026
- Участники: Максим (Участник 1), Сергей (Участник 2), Женя (Участник 3)
-
Вопрос: Использовать ли библиотеку FParsec для реализации парсера или писать парсер вручную?
-
Решение: Принято решение реализовать парсер вручную без использования FParsec или других сторонних библиотек.
-
Причина: Язык использует Lisp-like синтаксис (S-выражения), который легко парсится рекурсивно без необходимости сложных парсер-комбинаторов.
Ручная реализация: - проще в реализации для выбранного синтаксиса; - легче отлаживается; - лучше демонстрирует понимание работы парсера; - делает архитектуру проекта более прозрачной; - упрощает объяснение на защите.
Использование FParsec признано избыточным для текущего синтаксиса и усложняющим проект без значительного выигрыша.
- Дата: 02.05.2026
- Участники: Женя (Участник 3), Максим (Участник 1)
- Вопрос: Как дать Builtins.fs доступ к eval для вызова VClosure в map/filter/fold?
- Решение: makeBuiltins принимает eval как параметр.
Сигнатура меняется с
builtins : Map<string, Value>наmakeBuiltins : (Env -> Expr -> Result<Value, EvalError>) -> Map<string, Value>. - Причина: Чистое решение без циклических зависимостей и без смешения зон ответственности. Evaluator остаётся в своём файле, Builtins в своём — просто получает eval как аргумент.
- Дата: 02.05.2026
- Участники: Женя (Участник 3), Максим (Участник 1), Сергей (Участник 2)
- Вопрос: Как организовать использование
valueTypeNameв разных модулях (Evaluator.fs и Builtins.fs) без дублирования логики? - Решение: Вынести функцию
valueTypeNameв общий модуль (Values.fs), доступный для обоих компонентов. - Причина: Изначально рассматривались два варианта — дублирование функции в Builtins.fs или вынесение в общий модуль.
Дублирование позволяло быстро решить проблему локально, но приводило бы к расхождению логики и усложняло поддержку.
Вынесение в общий модуль: - устраняет дублирование кода; - обеспечивает единообразие сообщений об ошибках типов; - делает архитектуру более согласованной; - упрощает дальнейшее расширение набора типов значений.
- Дата: 03.05.2026
- Участники: Максим (Участник 1), Сергей (Участник 2), Женя (Участник 3)
- Вопрос: Как представить явную ленивость
delay/forceв AST? - Решение: Расширить
Exprотдельными формамиEDelayиEForce, а не моделироватьdelayиforceкак обычные вызовы встроенных функций. Парсер распознаёт(delay expr)и(force expr)как специальные формы, а evaluator создаёт и форсирует thunk-и через эти AST-узлы. - Причина:
delayне должен вычислять вложенное выражение до создания thunk-а, поэтому он не может быть обычной eager builtin-функцией. Отдельные AST-узлы явно фиксируют семантику special forms и разделяют ответственность: parser строит корректный AST, evaluator реализует отложенное вычисление, а builtins остаются для обычных строгих функций.