Перейти к содержанию

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 синтаксис с современным сахаром для лямбд через =>. Базовый вариант:
(let double (x => (* x 2))
  (double 10))

Поддерживаемые или желательные расширения: - многострочные лямбды без изменения семантики; - несколько аргументов через ((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 остаются для обычных строгих функций.