Re[2]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 26.03.23 22:07
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>В его мире, к примеру, нет "скаляров" или "плоских данных". Чтобы прибавить к двойке пятёрку нужно отправить объекту "2" сообщение "прибавить" с аргументом "5", где "5" — это ссылка на объект-пятёрку.

S>В ответ на это сообщение двойка пришлёт ссылку на другой объект типа "число". И значением этого числа будет "7".

И что мы будем делать в процессе ожидания ответа на сообщение?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[3]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 27.03.23 01:02
Оценка:
Здравствуйте, T4r4sB, Вы писали:
TB>И что мы будем делать в процессе ожидания ответа на сообщение?
То же, что и в ожидании возврата из функции Add(2, 5).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 27.03.23 08:41
Оценка:
Здравствуйте, korvin_, Вы писали:

V>>Макросы Лиспа подстановочные (шаблонные), как макросы Си/С++ или макроассемблера.

_>С чего это вдруг?

Такой дизайн. ))

Заметь, я не говорил, что в Лиспе нельзя производить трансформацию AST явным образом, если ты напишешь свой некий такой код.
Я напоминаю, как устроены стандарные макросы Лиспа.


V>>А вот макросы Форта устроены иначе — они получают на входе поток лексем и манипулируют с ним именно явно, порождая то самое AST.

_>Макросы Лиспа получают на вход дерево объектов и манипулируют с ним явно.

Разве?

(defmacro foo2 (x)
(list 'exp x))


В момент компиляции во внутренее представления встречающихся foo2 с аргументом x, де-факто будет происходить подстановка (list 'exp x) с указанным x.
Это обычный подстановочный макрос, который может быть реализован как текстуальный/шаблонный.
На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.

Никакого явного оперирования AST тут нет, как нет оперирования AST в шаблонах С++.


V>>Макросы Схемы живут только в момент компиляции, в отличии от макросов Лиспа

_>Что мешает в Схеме вызвать eval в рантайме?

Эта функциональность идёт в ввиде отдельной библиотеки-интерпретатора.
То бишь, это независимая динамическая реализация Схемы, если ты подключаешь библиотеку-интерпретатор в программу.

Соответственно, у подключаемого движка-интерпретатора нет никакой связи с твоей программой.
В Схеме eval имеет сигнатуру (eval expression environment) где environment необходимо набить ручками перед вызовом eval.
Сравнить с Лиспом, в котом eval имеет сигнатуру (eval expression), а environment используется неявным образом текущий.


V>>Макросы Схемы имеют другое ключевое слово

_>Это существенное отличие, да.

Это из-за дополнительных ключевых слов в телах макросов.
Т.е. чуть другой стандарт, поэтому взяли другой идентификатор, ИМХО.


V>>и пару особенностей.

_>Каких?

Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.
Там очевидно.


V>>Но это по-прежнему подстановочные/позиционные макросы, без явного манипулирования AST.

_>Что мешает работать с syntax object в Схеме через syntax-case, например?

Это что-то вроде частичного определения шаблонов в С++ — описание неких частных случаев.
Механика та же, что и для общего случая.


V>>Причём, тут я противоречу самому себе, вроде бы, ведь можно представить себе такую реализацию Схемы, которая ведёт себя как Лисп?

V>>Представить такую реализацию можно, да вот только Схема — это именно компиллируемая версия Лиспа. ))
V>>А динамическая версия — это сам Лисп.
_>SBCL прекрасно компилирует Common Lisp.

Да пофик, динамичность никуда не девается.
Всё-равно под капотом работает Лисп-машинка.


_>А eval из Схемы никуда не делся.


Он прямо по стандарту делся в отдельную библиотеку, которая подключается при надобности.
Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.

Соответственно, если широко используются ср-ва навроде eval (primitive-eval и т.д.), то Схема теряет заложенное в ней преимущество.
Профанация получается.


V>>Для целей эффективного конечного кода пришлось внести в Лисп несколько ограничений, наверно поэтому назвали другим языком и стандарты Схемы с тех пор развиваются независимо от Лиспа.

_>Схема не так и не для этого разрабатывалась.

Для этого. ))

Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных

Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.

Но при надобности не стирать символы — вот вам рядышком прежний динамический eval.


V>>Вернее, впервые стандарты на семейство Лисп появились в Схеме — именно из-за вносимых ограничений.

_>С чего ты взял, что из-за них?

Для обеспечения указанных св-в программы.
Там и выхода-то другого не было.
Поставь себя на место разработчиков такой системы, и вопросы отпадут сами собой.


V>>В любом случае, это лишь эмуляция.

_>Чем здесь эмуляция отличается от «не-эмуляции»?

Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризации по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.

При этом теряются гарантии целостности, в сравнении с С++, т.е. в эмуляции есть возможность описать объект, имеющий некорректный "бинарный лейаут", на котором эмуляция споткнётся в рантайм (в зависимости от реализации такой эмуляции может быть проход по памяти, либо же динамически выкинута ошибка/сигнал). В ООП-языках нет возможности описать объект некорректно, компилятор не позволяет. Даже в динамических, навроде JS или Питона, в них "объект" будет иметь корректный бинарный лейаут, "ошибки структуры объекта" будут сугубо семантические пользовательского уровня.


V>>Объекты в Лиспе не являются первоклассными сущностями языка, вот в чём косяк.

_>Являются.

В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.


V>>Первоклассной сущностью там являются числа/символы, функции и пара CONS(car, cdr), где каждый элемент пары может быть числом, функцией или опять парой (ссылочная семантика для всего перечисленного).

_>И много что ещё. Перечитай определение, что такое first-class citizen.

Сам почитай. ))

В C и C++ нельзя создавать функции во время выполнения программы, поэтому функции не являются объектами первого класса в этих языках. В то же время указатели на функцию можно передавать в качестве аргумента и возвращать из другой функции, поэтому иногда функции в C++ называют объектами второго класса (англ. second-class object). Тем не менее, в C++ есть понятие функционального объекта (англ. function object), который является объектом первого класса и реализует эквивалентную функциям семантику.

Русским по белому, что функциональный объект в С++ — это объект, эмулирующий семантику ф-ий.
Структура функторов в С++ известна, с т.з. компилятора — это пользовательский тип неизвестной семантики.
Т.е. компилятор умывает руки, просто делает то, что описано программистом.


V>>Форт навязывает лишь способ мышления, не навязывая остального.

V>>И при этом чудовищно прост в реализации.
V>>И при этом чудовищно эффективен, в сравнении с Лиспом.
V>>Порой и в сравнении с нейтивом из-за особенностей манипулирования стеком — в Форте обычно меньше паразитных манипуляций данными в стеках/регистрах, меньше ненужных копирований/преобразований данных — состояние стека на выходе одного слова часто является готовым состоянием стека для вызова другого. Писать программы на Форте — это как играть в Тетрис, натурально, все фигуры должны стыковаться, и тогда прога просто летает. ))
_>А уж ассемблер-то как эффективен.

Мимо. Ассемблер низкоуровневый язык, а Форт высокоуровневый, хотя имеет ср-ва низкого уровня.
Это примерно как Си, только потенциально мощнее, т.к. позволяет описывать свой синтаксис (DSL) в программе.

Но это всё не бесплатно, конечно, т.к. простор для ошибок в Форте достаточно широк.
На технике тех лет за эффективность приходилось платить. ))


_>И что из этого вытекает? Зачем все эти Лиспы, Си, Паскали и прочие Форты понапридумывали?


Да тут по классике инженерии — это решения в условиях противоречивых требований, где само решение представляет из себя системы принятых компромиссов.
Развитие ЯВУ диктовалось развитием ресурсов железа, конечно, т.е. сугубо практическими соображениями.
Например, функциональные языки своего слова еще не сказали, т.е. тут как в науке — теория значительно опережает практику. ))

В одном из встреченных экспериментов проводили альфа/бета/эта-преобразования программы (эти преобразования выполняются итеративно, пока есть возможность таких преобразований) на ФП-языке "до самого дна" среднего размера программы.

При "окончательном" таком преобразовании пользовательские типы данных и ф-ии стираются/склеиваются, т.е. в итоге склеивается идентичный код, работающий над различными типами. Плюс "раскрытие скобок" и прочая подстановка бета-редукции, с возможностью заменять ссылочную семантику на значения и т.д. и т.п.

Компиляция на современной технике заняла несколько дней.
Вот тебе ответ, зачем другие языки. ))
Отредактировано 27.03.2023 11:34 vdimas . Предыдущая версия . Еще …
Отредактировано 27.03.2023 10:49 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:47 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:44 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:43 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 10:40 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:50 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:47 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:44 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:44 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:43 vdimas . Предыдущая версия .
Отредактировано 27.03.2023 8:42 vdimas . Предыдущая версия .
Re[13]: понимание ООП Алана Кея
От: korvin_  
Дата: 27.03.23 10:08
Оценка: 2 (1)
Здравствуйте, vdimas, Вы писали:

V>Такой дизайн. ))


Какой такой дизайн?

V>Заметь, я не говорил, что в Лиспе нельзя производить трансформацию AST явным образом, если ты напишешь свой некий такой код.


Тогда зачем ты «противопоставил» их Форту?

V>Я напоминаю, как устроены стандарные максросы Лиспа.


Тогда что значит «подстановочные»?

V>Разве?

V>

V>(defmacro foo2 (x)
V> (list 'exp x))


Разве. x — любое S-выражение, либо дерево, либо атом. Обрабатывай как хочешь.

V>В момент компиляции во внутренее представления встречающихся foo2 с аргументом x, де-факто будет происходить подстановка (list 'exp x)) с указанным x.


Потому что ты его так определил.

V>Это обычный подстановочный макрос, который может быть реализован как текстуальный/шаблонный.


Но работает он не с текстом, а с lisp-объектами.

V>На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.


Не вроде.

V>Никакого явного оперирования AST тут нет, как нет оперирования AST в шаблонах С++.


Так ты никак не оперируешь, потому что такой макрос написал. Макрос loop по-твоему тоже никак не оперирует?

(defun bindingp (expr)
  (and (= (length expr) 3)
       (destructuring-bind (var eq-sym val-expr) expr
         (eq eq-sym '=))))

(defmacro my-let (&rest body)
  (loop :while (bindingp (first body))
        :collect (first (first body)) :into variables
        :collect (third (first body)) :into expressions
        :do (setf body (rest body))
        :finally (return `((lambda ,variables ,@body) ,@expressions))))

(defun main ()
  (my-let (x = 1) (y = 2)
    (print (+ x y))))


Или что ты подразумеваешь под «оперированием AST»?

V>Эта функциональность идёт в ввиде отдельной библиотеки-интерпретатора.

V>То бишь, это независимая динамическая реализация Схемы, если ты подключаешь библиотеку-интерпретатор в программу.

V>Соответственно, у подключаемого движка-интерпретатора нет никакой связи с твоей программой.

V>В Схеме eval имеет сигнатуру (eval expression environment) где environment необходимо набить ручками перед вызовом eval.
V>Сравнить с Лиспом, в котом eval имеет сигнатуру (eval expression), а environment используется неявным образом текущий.

И что, невозможно выполнить в рантайме, в пользовательском eval Схемы define-syntax?

V>Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.

V>Там очевидно.

Тебе сложно перечислить парочку?

V>>>Но это по-прежнему подстановочные/позиционные макросы, без явного манипулирования AST.

_>>Что мешает работать с syntax object в Схеме через syntax-case, например?

V>Это что-то вроде частичного определения шаблонов в С++ — описание неких частных случаев.

V>Механика та же, что и для общего случая.

ничего общего с шаблонами C++. И это не некие частные случаи, а возможность обрабатывать syntax-object явным образом с помощью обычных функций. Это, как раз общий случай, а syntax-rules — частный, упрощённый способ.

V>Да пофик, динамичность никуда не девается.

V>Всё-равно под капотом работает Лисп-машинка.

Как и Схем-машинка в Схеме.

_>>А eval из Схемы никуда не делся.


V>Он прямо по стандарту делся в отдельную библиотеку, которая подключается при надобности.


По которому стандарту и в какую библиотеку?

Вот смотрю, например, R5RS и не вижу упоминания библиотек.

Вот R7RS, eval наместе. Про библиотеку не сказано.

V>Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.


Откуда ты взял эту идею? У cons-ячеек в Схеме ссылочная семантика и это используется в SICP.


V>>>Для целей эффективного конечного кода пришлось внести в Лисп несколько ограничений, наверно поэтому назвали другим языком и стандарты Схемы с тех пор развиваются независимо от Лиспа.

_>>Схема не так и не для этого разрабатывалась.

V>Для этого. ))

V>

V>Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных


Это не говорит о цели разработки. И к компиляции/интерпретации отношения не имеет.

V>Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.


SBCL умеет в оптимизацию хвостовой рекурсии. А в Racket есть динамический контекст в виде parameters.

V>Но при надобности не стирать символы — вот вам рядышком прежний динамический eval.



V>>>Вернее, впервые стандарты на семейство Лисп появились в Схеме — именно из-за вносимых ограничений.

_>>С чего ты взял, что из-за них?

V>Для обеспечения указанных св-в программы.

V>Там и выхода-то другого не было.
V>Поставь себя на место разработчиков такой системы, и вопросы отпадут сами собой.

Зачем мне ставить себя на их место, какое это имеет отношение к стандратизации?

V>Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризацию по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.


Но при использовании CLOS этого не нужно описывать явно. Всё описано в библиотеке. И какая разница, находится это в библиотеке или зашито в рантайм языка?

V>При этом теряются гарантии целостности, в сравнении с С++, т.е. в эмуляции есть возможность описать объект, имеющий некорректный "бинарный лейаут", на котором эмуляция споткнётся в рантайм (в зависимости от реализации такой эмуляции может быть проход по памяти, либо же динамически выкинута ошибка/сигнал). В ООП-языках нет возможность описать объект некорректно, компилятор не позволяет. Даже в динамических, навроде JS или Питона, в них "объект" будет иметь корректный бинарный лейаут, "ошибки структуры объекта" будут сугубо семантические пользовательского уровня.


Какие гарантии целостности теряются в CLOS?

V>В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.


И что с того? Следует ли тогда стремиться вообще всё известное впихнуть в рантайм языка?

V>>>Первоклассной сущностью там являются числа/символы, функции и пара CONS(car, cdr), где каждый элемент пары может быть числом, функцией или опять парой (ссылочная семантика для всего перечисленного).

_>>И много что ещё. Перечитай определение, что такое first-class citizen.

V>Сам почитай. ))

V>

V>В C и C++ нельзя создавать функции во время выполнения программы, поэтому функции не являются объектами первого класса в этих языках. В то же время указатели на функцию можно передавать в качестве аргумента и возвращать из другой функции, поэтому иногда функции в C++ называют объектами второго класса (англ. second-class object). Тем не менее, в C++ есть понятие функционального объекта (англ. function object), который является объектом первого класса и реализует эквивалентную функциям семантику.

V>Русским по белому, что функциональный объект в С++ — это объект, эмулирующий семантику ф-ий.
V>Структура функторов в С++ известна, с т.з. компилятора — это пользовательский тип неизвестной семантики.
V>Т.е. компилятор умывает руки, просто делает то, что описано программистом.

При чём тут C и C++, когда речь шла о Лиспе? Кроме того непонятно, что автор имел в виду под «созданием функций во время выполнения программы». Что мешает подключить интерпретатор С в программу?
Re[4]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 27.03.23 17:27
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>То же, что и в ожидании возврата из функции Add(2, 5).


В случае с функцией ожидания никакого нет, в том-то и дело. А с сообщением как?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[5]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 01:46
Оценка: +1
Здравствуйте, T4r4sB, Вы писали:
TB>В случае с функцией ожидания никакого нет, в том-то и дело. А с сообщением как?
Точно так же. Сообщение — это метафора, а не физическая реализация.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[11]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 01:49
Оценка:
Здравствуйте, vdimas, Вы писали:
V>Если тебе интересно закрыть эту тему раз и навсегда — распишу весь код целиком.

Да, было бы интересно. Брать прямо словарь не обязательно — там много нюансов. Можно использовать какую-то более простую структуру.
Но, сдаётся мне, что иммутабельность будет там обеспечена дизайном типа, а не ключевым словом const.
То есть в лучшем случае вы получите решение, изоморфное System.Collections.Immutable.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 28.03.23 06:53
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Точно так же. Сообщение — это метафора, а не физическая реализация.


И что делать потоку во время этой ожидания этой метафоры? Засыпать?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[7]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 07:18
Оценка:
Здравствуйте, T4r4sB, Вы писали:
TB>И что делать потоку во время этой ожидания этой метафоры? Засыпать?
Повторю свой ответ: то же самое, что делает поток в ожидании возврата из процедуры. Ведь вызов процедуры — это тоже метафора, на самом деле никаких процедур нету, есть только стек и регистры.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 13:01
Оценка:
Здравствуйте, Sinclair, Вы писали:

V>>Если тебе интересно закрыть эту тему раз и навсегда — распишу весь код целиком.

S>Да, было бы интересно. Брать прямо словарь не обязательно — там много нюансов. Можно использовать какую-то более простую структуру.
S>Но, сдаётся мне, что иммутабельность будет там обеспечена дизайном типа, а не ключевым словом const.

Ключевое слово const даст гарантии сильнее "вербальных соглашений", которые в реальной жизни не обеспечиваются ничем, кроме добросовестности программиста. ))
Даже правильно спроектированный под конкретную задачу тип затем может быть поломан из-за невнимательности, при добавлении мутабельных методов.

Общая идея при конструировании иммутабельных типов от неиммутабельных с использованием паттерна Decorator — это сочетания конструктора перемещения с константным хранением перемещённых данных.
template<typename T>
class ImmutableValue {
    const T value_;

public:
    ImmutableValue(T && value) : value_(value) {}


Добавим делегирующие операторы сравнения для ключей словарей на основе дерева:
    bool operator==(const ImmutableValue<T> & other) const {
        return value_ == other.value_;
    }

    bool operator!=(const ImmutableValue<T> & other) const {
        return value_ != other.value_;
    }
    

    bool operator<(const ImmutableValue<T> & other) const {
        return value_ < other.value_;
    }


Добавим делегирущее вычисление хеша для ключей хеш-таблиц:
    operator const T &() const { return value_; }
};

template<typename T>
struct std::hash<ImmutableValue<T>> {
    size_t operator()(const ImmutableValue<T> & ikey) const {
        return hash<T>()(ikey);
    }    
};


Проверяем:
template<typename T>
size_t hash_value(const T & value) {
    return std::hash<T>()(value);
}

int main() {
    ImmutableKey<int> i = 42;
    std::cout << hash_value(i) << endl;

    return 0;
}


Для оберток типов-отображений (назовём ImmutableMap) общая структура типа будет такая же.
По твоей просьбе опускаю часть контракта (typedef-ы), вот целевой делегирующий метод:

const TValue & operator[](const TKey & key) const { return map_[key]; }


Далее в своём коде используешь шаблонный ImmutableMap.


S>То есть в лучшем случае вы получите решение, изоморфное System.Collections.Immutable.


В С++ ключевое слово const применимо так же к полям структур/классов.
Такие поля могут быть инициализированы только в конструкторе.
Это аналог readonly в C#.

И зря ты ссылаешься на Collections.Immutable.
Это не столько про саму иммутабельность, сколько про специальные алгоритмы на иммутабельных графах и списках.
Соответственно, ценностью этого раздела библиотеки является уже готовая функциональность, а не какие-то там гарантии.
(никаких гарантий та система типов не даёт)

Для сравнения, в С++ можно расписать аналогичную функциональность с "железобетонными" гарантиями иммутабельности.

Основные пляски вокруг иммутабельности в ФП, насколько я на них натыкался, они происходят не из-за агоритмов навроде Collections.Immutable (это лишь узкий частный случай), а из-за более широкой задачи — из-за статической и динамической альфа-бета-эта-редукции в процессе компиляции и исполнения программ.

Смотрим, что происходит при частичном связывании аргументов для некоей ф-ии/лямбд:
Func<double, double, double> fn1 = (x, y) => sin(x) + sin(y);
Func<double, double> fn2 = (x) => fn1(x, Math.Pi);

В C# после связывания, тело fn2 будет честно вызывать тело fn1, где снова и снова будет вызываться sin(Math.Pi).

В функциональных языках есть принципиальная возможность бета-эта-редуцировать не только в compile-time, но и в рантайме при связывании переменных.
В Хаскеле это делается через мемоизацию ленивых вычислений, что является достаточно убогой реализацией, ес-но — обслуживается структура из флага и поля для целевого значения, страдает поток вычислений в стеке и на конвейере проца.

В идеале предпочтительна трансформация самого тела fn2 в рантайм, т.е. порождение редуцированного кода на-лету (например, в параллельном потоке), т.е. целевой код может вызывать некоторое время нередуцированную fn, пока в фоне из ее тела не породят оптимизированный редуцированный вариант, а далее тело подменяется безопасно на лету опять же из-за природы иммутабельности всего и вся.

Для данного выражения имеем sin(Math.Pi)==0, тогда fn2=(x)=>sin(x), тогда через эта-преобразование вызовы fn2(x) заменяются на вызовы просто sin(x).
В С++ подобное редуцирование выполняется лишь для констант времени компиляции и только на некую ограниченную грубину распространения констант.

Вдогонку, возвращаясь к иммутабельным графам — распространение констант (связывание термов) возможно не только в тривиальных случаях, но и по всем данным.
Вплоть до подмены и стирания типов (это про альфа-преобразование — идентично описанные с точностью до "раскрытия скобок" термы-типы можно считать одинаковыми "унутре").
Вплоть до того, что целые куски таких иммутабельных графов, по которым бегает некий вычислитель, могут подменяться просто на значение. ))
Отредактировано 28.03.2023 13:17 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 13:13 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:12 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:08 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:07 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:06 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:04 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 13:02 vdimas . Предыдущая версия .
Re[12]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 15:05
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>То есть в лучшем случае вы получите решение, изоморфное System.Collections.Immutable.


Вдогонку, у дотнета уже были интерфейсы, описывающие АПИ иммутабельных коллекций, навроде IReadOnlySet и т.д.
Выбранное название неймспейса сбивает с толку, бо там речь не об иммутабельности как таковой, а об эффективных способах порожения read-only коллекций. ))

Например, неэффективной будет реализация на основе некоего FrozenSet, т.к. потребует копирования коллекций целиком при порождении новых коллекций, т.е. "изменения" их в ФП-стиле.

Наверно, стоило этот неймспейс так и назвать FpStyle. ))
Re[13]: понимание ООП Алана Кея
От: Sinclair Россия https://github.com/evilguest/
Дата: 28.03.23 16:04
Оценка:
Здравствуйте, vdimas, Вы писали:
[сcode]
template<typename T>
class ImmutableValue {
const T value_;

public:
ImmutableValue(T && value) : value_(value) {}
[/cсode]
Хм. А там точно будет ссылка на оригинал, а не дубликат? Похоже, вы свой код не проверяли.
А если "исправить" ваш код так:
template<typename T>
class ImmutableValue {
    const T& value_;

то мы легко убедимся, что чудес не бывает.
Покажите мне пример кода, в котором вы изготавливаете из мутабельного объекта иммутабельный, и продемонстрируйте, что вы после этого не можете этот иммутабельный изменить.

V>Далее в своём коде используешь шаблонный ImmutableMap.

Вот прямо так сразу использовать шаблонный ImmutableMap не получится. Вы, как водится, привели тот самый фрагмент кода, который был очевиден и без обсуждения.
Как вы реализуете метод add(const TKey & key, const TValue & value)?
Внезапно окажется, что нельзя просто взять произвольный mutable тип, завернуть его в ImmutableValue и наслаждацца. Даже если вы сможете реализовать своё обещание с "zero-overhead" конструктором перемещения (в чём я почему-то сомневаюсь), реализация изменяющих методов "в лоб" потребует от вас реализовать copy-on-write, что убъёт производительность.
Чтобы ваш "мутабельно-иммутабельный" словарь можно было применять в реальной жизни, придётся особенным образом проектировать его мутабельную версию. Посмотрите для примера в код классов ImmutableXXX.Builder всё в том же неймспейсе.


V>В С++ ключевое слово const применимо так же к полям структур/классов.

V>Такие поля могут быть инициализированы только в конструкторе.
V>Это аналог readonly в C#.
Это всё понятно. Да, слово const есть, можно применять его к мемберам. Счастье-то в чём?

V>Это не столько про саму иммутабельность, сколько про специальные алгоритмы на иммутабельных графах и списках.

Чегось? Какие ещё алгоритмы? Какие графы? Там ровно то, что написано в названии неймспейса — реализация иммутабельного списка, словаря, множества, массива, стека, очереди, и ещё пары классов.

V>Соответственно, ценностью этого раздела библиотеки является уже готовая функциональность, а не какие-то там гарантии.

V>(никаких гарантий та система типов не даёт)
Конечно даёт.
Вот мой метод:
public void RegisterState(ImmutableDictionary<string, int> state)
{
  _visited[state] = true;
}

Попробуйте "сломать" его, передав в него мутабельный state.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: понимание ООП Алана Кея
От: T4r4sB Россия  
Дата: 28.03.23 16:59
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Повторю свой ответ: то же самое, что делает поток в ожидании возврата из процедуры.


Поток в это время выполняет эту самую процедуру.
А объект и сообщение — это получается по факту просто вызов метода?
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[14]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 17:01
Оценка:
Здравствуйте, korvin_, Вы писали:

V>>Такой дизайн. ))

_>Какой такой дизайн?

Примитивный.
Что было ОК для годов разработки Лиспа. ))


V>>Заметь, я не говорил, что в Лиспе нельзя производить трансформацию AST явным образом, если ты напишешь свой некий такой код.

_>Тогда зачем ты «противопоставил» их Форту?

Из-за разных стадий происходящего.
Макросы Лиспа работают на стадии компиллирования текста во внутреннее представление, т.е. макросы обслуживаются низкоуровневым (типа ассемблерным или сишным) кодом, смотря на чём реализация Лиспа была написана. Твои высокоуровневые примитивы не вызываются. Трансформацию AST можно выполнять лишь уже в процессе работы Лисп-машинки твоим высокоуровневым лисповым кодом, к макросам это никаким боком.

Для сравнения, в Форте можно курочить код-как-данные на любой стадии.
Т.е. ТВОЙ высокоуровневый код в телах макросов может работать прямо на стадии компиляции во внутренее представление.
У Лиспа никакого немедленного вызова пользовательских ф-ий в макросах нет и быть не может. ))


V>>Я напоминаю, как устроены стандарные максросы Лиспа.

_>Тогда что значит «подстановочные»?

Это когда некий шаблонный движок по неким правилам производит замену подстрок.
Я же говорил — технически макросы Лиспа могут быть реализованы сугубо текстовой заменой подстрок, а после замены получившаяся новая строка скармливается компилятору.
Примерно так работали первые реализации макросов в Си/С++.
Сейчас там макродвижок работает как в Лиспе — по предкомпилённому представлению.
Но! Это лишь подробности реализации.
Текстуально-шаблонная природа таких макросов целиком определяет их св-ва.


V>>Разве?

V>>

V>>(defmacro foo2 (x)
V>> (list 'exp x))

_>Разве. x — любое S-выражение, либо дерево, либо атом. Обрабатывай как хочешь.

x никак не обрабатывается в момент раскрытия макроса компилятором.
Он может быть обработан уже в рантайме кодом, в который раскрылся макрос.

Можно привести в пример не Форт, а, допустим, Nemerle.
В этом языке тоже есть возможность "вмешиваться в работу компилятора", то бишь в момент раскрытия макроса могут вызываться высокоуровневые примитивы.
Вот там макрос вполне может работать над AST и работает.
В Лиспе — нет, никогда и низачто! ))


V>>В момент компиляции во внутренее представления встречающихся foo2 с аргументом x, де-факто будет происходить подстановка (list 'exp x)) с указанным x.

_>Потому что ты его так определил.

Потому что это семантика сугубо текcтовой шаблонизации.


V>>Это обычный подстановочный макрос, который может быть реализован как текстуальный/шаблонный.

_>Но работает он не с текстом, а с lisp-объектами.

Макрос не работает с лисп-объектами, он работает с текстовым представлением исходника.
В этом месте я начинаю подозревать, что ты путаешь работу макроса и работу кода, порождённого макросом. ))


V>>На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.

_>Не вроде.

Вроде, вроде.
Абсолютно в точности.


V>>Никакого явного оперирования AST тут нет, как нет оперирования AST в шаблонах С++.

_>Так ты никак не оперируешь, потому что такой макрос написал. Макрос loop по-твоему тоже никак не оперирует?

loop — это конструкт, т.е. встроенная в компилятор хрень.
Или макрос над таким конструктом.

Но может быть реализован в виде обычной ф-ии.
В виде конструкта он сугубо по соображениям эффективности, скорее всего, иначе бы все эти служебные for, to, return должны были бы быть атомами и тело loop должно было бы производить сравнение с ними каждого элемента списка-тела.

Макросы могут содержать другие макросы, раскрываются рекурсивно.
И ты уверен, что loop — это макрос, а не встроенный конструкт?
(в крайней случае макрос над конструктом?)


_>Или что ты подразумеваешь под «оперированием AST»?


Дело не в оперировании AST, а на какой стадии это происходит.
Если в рантайме, то исходная AST зачастую является информационным мусором, лишними данными, занимающими память, т.к. интересует лишь конечное представление.
Например, тело loop со всякими for, to, return — это синтаксис, его не надо хранить явным образом для исполнения.


V>>Соответственно, у подключаемого движка-интерпретатора нет никакой связи с твоей программой.

V>>В Схеме eval имеет сигнатуру (eval expression environment) где environment необходимо набить ручками перед вызовом eval.
V>>Сравнить с Лиспом, в котом eval имеет сигнатуру (eval expression), а environment используется неявным образом текущий.
_>И что

То, что в этом месте вопросы про eval Схемы должны были закончится.


V>>Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.

V>>Там очевидно.
_>Тебе сложно перечислить парочку?

Мне сложно отвечать на слабосформулированные вопросы, бо обсуждение теряет темп и смысл.
Нормальное обсуждение состоит из тезисов и антитезисов.
Есть что сказать — говори.


_> ничего общего с шаблонами C++. И это не некие частные случаи, а возможность обрабатывать syntax-object явным образом с помощью обычных функций.


Да каких обычных ф-ий? ))
syntax-case выбирает вариант применения шаблона.
То бишь, в одном шаблоне несколько их, syntax-case матчит шаблон.

Просто схема пошла дальше Лиспа и оперирует уже не исходным текстом, а цепочками токенов.
Но какая фиг разница, подстановка чего выполняется — терминалов или нетерминалов?
Т.е. последовательность ASII-символов или токенов из них?
Это все-равно достаточно тупая подстановка.


V>>Да пофик, динамичность никуда не девается.

V>>Всё-равно под капотом работает Лисп-машинка.
_>Как и Схем-машинка в Схеме.

Нифига Схеме "машинка"?
Машинка нужна Лиспу для следующего:
— динамического поиска символов в текущем контексте (угу, даже если это Лисп-компилятор);
— манипулирования этим контектом для каждой ф-ии, чтобы сохранялась семантика, идентичная семантике интерпретатора.
— все данные строго ссылочные, исходные лисповые тела ф-ий хранятся в явном виде имогут быть исполнены через eval в текущем контексте.

И напротив, в Схеме нет оперирования текущим контекстом, нет интерпретатора тел функций, т.е. нет и Схем-машинки (кроме сбоку-припёку, которая опциональна для нужд eval БЕЗ связи с твоей программой, т.е. не нужна в общем случае).

В общем, бета-эта-редукции в Схеме выполняются аж бегом компилятором.
Когда не требуется явно оперировать контекстом исполнения и символы стираются (т.к. область видимости статическая, т.е. символы ресолвятся на этапе компиляции, а не в процессе работы) — у компилятора развязываются руки.

Становится не обязательным в большиснстве случаев представлять данные как связанные cons — можно проводить escape-анализ и удалять ссылочную семантику (для банальных чисел) и т.д. до бесконечности.


_>Вот смотрю, например, R5RS и не вижу упоминания библиотек.

_>Вот R7RS, eval наместе. Про библиотеку не сказано.

Возможно. Не принципиально.
Я смотрел несколько реализаций Схем, когда писал свою реализацию для дотнета, видел что eval был оформлен в отдельную либу.


V>>Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.

_>Откуда ты взял эту идею? У cons-ячеек в Схеме ссылочная семантика и это используется в SICP.

Это исходная семантика ссылочная, а компилятор вправе делать преобразования над данными.
Например, зачем тут ссылочная семантика (sin (+ x 5)), если компилятор видит область действия x целиком, т.е. знает, что оно не убегает.


V>>Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных

_>Это не говорит о цели разработки. И к компиляции/интерпретации отношения не имеет.


Статическая область видимости символов позволяет отказаться от хранения их имён в скомпиллированном образе, т.к. все символы ресолвятся еще на стадии компиляции.
А значит, не нужен пресловутый лисповый контекст, в котором хрен ты так просто вызовешь некую функцию fn, сначала Лисп-машинка найдёт в этом контексте текущее (т.е. ближайшее) определение символа fn, убедится, что это функция, и подаст ей хвост выражения.


V>>Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.

_>SBCL умеет в оптимизацию хвостовой рекурсии.

В условиях динамического контекста Лиспа я бы не говорил "гоп" без тщательных экспериментов — какие именно случаи поддаются раскрутке.


V>>Для обеспечения указанных св-в программы.

V>>Там и выхода-то другого не было.
V>>Поставь себя на место разработчиков такой системы, и вопросы отпадут сами собой.
_>Зачем мне ставить себя на их место, какое это имеет отношение к стандратизации?

Наверно такое, что интерпретатор Лиспа — это дешевая в разработке хрень (несколько вечеров на коленке достаточно), а компилятор Схемы — дорогая разработка.
Соотв., стандартизация нужна, чтобы дорогой труд не пропадал даром.


V>>Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризацию по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.

_>Но при использовании CLOS этого не нужно описывать явно. Всё описано в библиотеке.

Это и есть явно, т.е. на библиотечном уровне, а не встроенными ср-вами языка.
В С++ устройство объекта неизвестно, механика диспетчеризации виртуальных ф-ий неизвестна, механика множественного и виртуального множественного наследования неизвестна.
Мы можем знать только реверс-инжинирив конкретные реализации (ну и обладая эрудицией о том, как это вобще реализуется), но с т.з. языка устройство объекта неизвестно.


_>И какая разница, находится это в библиотеке или зашито в рантайм языка?


Говорил уже — компилятор не может породить невалидный лейаут объекта в памяти.
А к тебе в библиотеку запросто можно подать невалидный лейаут объекта.


_>Какие гарантии целостности теряются в CLOS?


Их там просто нет.
Ты можешь случайным образом подать методу одного объекта инстанс другого.


V>>В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.

_>И что с того? Следует ли тогда стремиться вообще всё известное впихнуть в рантайм языка?

И в компилятор, и в рантайм.
Разумеется, развитие ЯВУ идёт в направлении повышения эффективности программиста, машина должна брать на себя максимум.


_>При чём тут C и C++, когда речь шла о Лиспе?


В том абзаце ты возражал против моего понимания первоклассной сущности.
ООП-объекты в Лиспе не являются первоклассными.


_>Кроме того непонятно, что автор имел в виду под «созданием функций во время выполнения программы».


Лямбды, карринг.


_>Что мешает подключить интерпретатор С в программу?


Зачем интерпретатор? ))

В С++ можно описывать функциональные объекты — функторы.
Но они не являются первоклассными сущностями-функциями, они являются первоклассными сущностями-объектами, у которых определён метод 'operator()'.
Отредактировано 28.03.2023 17:59 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 17:21 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 17:03 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 17:02 vdimas . Предыдущая версия .
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 18:11
Оценка: +1
Здравствуйте, vdimas, Вы писали:

V>Макросы Лиспа работают на стадии компиллирования текста


Нет. Макросы Лиспа работают после read, который преобразует текст в S-выражение.

V> т.е. макросы обслуживаются низкоуровневым (типа ассемблерным или сишным) кодом, смотря на чём реализация Лиспа была написана.


Макросы Лиспа — это обычные функции, которые выполняются во время компиляции, получают на вход невычесленное S-выражение и преобразуют/обрабатывают его ровно так, как это описано в теле макроса.

V> Твои высокоуровневые примитивы не вызываются.


Какие мои высокоуровневые примитивы не вызываются? В теле макроса вызывается любой Лисп-код, который я напишу.

V> Трансформацию AST можно выполнять лишь уже в процессе работы Лисп-машинки твоим высокоуровневым лисповым кодом, к макросам это никаким боком.


Эм нет. В процессе работы Лисп-машины никакого AST уже может не быть, а быть только байткод или машкод.

V>Для сравнения, в Форте можно курочить код-как-данные на любой стадии.


И?

V>Т.е. ТВОЙ высокоуровневый код в телах макросов может работать прямо на стадии компиляции во внутренее представление.


И? А если я вызываю compile в рантайме — это стадия компиляции или стадия выполнения?

V>У Лиспа никакого немедленного вызова пользовательских ф-ий в макросах нет и быть не может. ))


Т.е. ты совершенно не понимаешь, как работают макросы в Лиспе. Именно вызов функции там и происходит.

V>>>Я напоминаю, как устроены стандарные максросы Лиспа.

_>>Тогда что значит «подстановочные»?

V>Это когда некий шаблонный движок по неким правилам производит замену подстрок.


Значит, не понимаешь. В CL нет никакого шаблонного движка.

V>Я же говорил — технически макросы Лиспа могут быть реализованы сугубо текстовой заменой подстрок, а после замены получившаяся новая строка скармливается компилятору.


Не могут.

V>Примерно так работали первые реализации макросов в Си/С++.


Но к Лиспу они отношения не имеют.

V>Сейчас там макродвижок работает как в Лиспе — по предкомпилённому представлению.

V>Но! Это лишь подробности реализации.
V>Текстуально-шаблонная природа таких макросов целиком определяет их св-ва.

Ты что-то перепутал.

V>>>Разве?

V>>>

V>>>(defmacro foo2 (x)
V>>> (list 'exp x))

_>>Разве. x — любое S-выражение, либо дерево, либо атом. Обрабатывай как хочешь.

V>x никак не обрабатывается в момент раскрытия макроса компилятором.

V>Он может быть обработан уже в рантайме кодом, в который раскрылся макрос.

Вообще-то ты вызываешь здесь функцию list в compile-time и никакого вызова list в рантайме (в коде, в который раскрылся макрос) уже не будет. Возьми macroexpand, да посмотри.

V>Можно привести в пример не Форт, а, допустим, Nemerle.

V>В этом языке тоже есть возможность "вмешиваться в работу компилятора", то бишь в момент раскрытия макроса могут вызываться высокоуровневые примитивы.

В CL в момент раскрытия макроса можно вызывать что угодно.

V>Вот там макрос вполне может работать над AST и работает.

V>В Лиспе — нет, никогда и низачто! ))

Ты бредишь.

V>Потому что это семантика сугубо текcтовой шаблонизации.




V>Макрос не работает с лисп-объектами, он работает с текстовым представлением исходника.


Нет. Возьми любую книгу по CL да почитай. Или сам macroexpand'ом воспользуйся, да проверь, что попадёт в результат, а что — нет.

V>В этом месте я начинаю подозревать, что ты путаешь работу макроса и работу кода, порождённого макросом. ))


Нет, просто ты совершенно не понимаешь, как работают макросы в Лиспе.

V>>>На практике в Лиспе он не текстуальный по соображениям эффективности — тело макроса хранится в предкомпилённом виде, т.е. что-то вроде шаблонов С++.

_>>Не вроде.

V>Вроде, вроде.

V>Абсолютно в точности.

Совершенно нет.

V> loop — это конструкт, т.е. встроенная в компилятор хрень.

V> Или макрос над таким конструктом.

Выполни macroexpand да посмотри. А заодно можешь посмотреть реализацию.

V>Но может быть реализован в виде обычной ф-ии.


Может, но придётся обмазываться лямбдами, чтобы отложить вычисления выражений.

V>В виде конструкта он сугубо по соображениям эффективности, скорее всего, иначе бы все эти служебные for, to, return должны были бы быть атомами и тело loop должно было бы производить сравнение с ними каждого элемента списка-тела.


Посмотри реализацию и macroexpand.

V>Макросы могут содержать другие макросы, раскрываются рекурсивно.

V>И ты уверен, что loop — это макрос, а не встроенный конструкт?

Да.

V>(в крайней случае макрос над конструктом?)


Над каким конструктом?

_>>Или что ты подразумеваешь под «оперированием AST»?


V>Дело не в оперировании AST, а на какой стадии это происходит.

V>Если в рантайме, то исходная AST зачастую является информационным мусором, лишними данными, занимающими память, т.к. интересует лишь конечное представление.
V>Например, тело loop со всякими for, to, return — это синтаксис, его не надо хранить явным образом для исполнения.

А если в compile-time?

V>То, что в этом месте вопросы про eval Схемы должны были закончится.


Ты на них не ответил.

V>>>Да просто открываешь доку по макросам Лиспа и Схемы, сравниваешь.

V>>>Там очевидно.
_>>Тебе сложно перечислить парочку?

V>Мне сложно отвечать на слабосформулированные вопросы, бо обсуждение теряет темп и смысл.

V>Нормальное обсуждение состоит из тезисов и антитезисов.
V>Есть что сказать — говори.

Пока что я могу сказать, что ты не понимаешь предмета разговора.

_>> ничего общего с шаблонами C++. И это не некие частные случаи, а возможность обрабатывать syntax-object явным образом с помощью обычных функций.


V>Да каких обычных ф-ий? ))


Например syntax-car, syntax-cdr и так далее.

V>syntax-case выбирает вариант применения шаблона.


Лишь для удобства, а можно просто матчить с _ и обрабатывать функциями. Любыми доступными и/или опредедёнными.

(define-syntax let
  (lambda (x)
    (define ids?
      (lambda (ls)
        (or (null? ls)
            (and (identifier? (car ls)) (ids? (cdr ls))))))
    (define unique-ids?
      (lambda (ls)
        (or (null? ls)
            (and (not (memp
                        (lambda (x) (bound-identifier=? x (car ls)))
                        (cdr ls)))
                 (unique-ids? (cdr ls))))))
    (syntax-case x ()
      [(_ ((i e) ...) b1 b2 ...)
       (and (ids? #'(i ...)) (unique-ids? #'(i ...)))
       #'((lambda (i ...) b1 b2 ...) e ...)])))


With this mechanism, transformers are procedures of one argument. The argument is a syntax object representing the form to be processed. The return value is a syntax object representing the output form. A syntax object may be any of the following.

a nonpair, nonvector, nonsymbol value,
a pair of syntax objects,
a vector of syntax objects, or
a wrapped object.



V>Просто схема пошла дальше Лиспа и оперирует уже не исходным текстом, а цепочками токенов.


Лисп не оперирует исходным текстом.

V>Но какая фиг разница, подстановка чего выполняется — терминалов или нетерминалов?

V>Т.е. последовательность ASII-символов или токенов из них?
V>Это все-равно достаточно тупая подстановка.

Это что угодно, от тупой подстановки, до выполнения любого произвольного кода.

V>>>Да пофик, динамичность никуда не девается.

V>>>Всё-равно под капотом работает Лисп-машинка.
_>>Как и Схем-машинка в Схеме.

V>Нифига Схеме "машинка"?


За тем же, зачем и любому другому языка.

V>Машинка нужна Лиспу для следующего:

V>- динамического поиска символов в текущем контексте (угу, даже если это Лисп-компилятор);
V>- манипулирования этим контектом для каждой ф-ии, чтобы сохранялась семантика, идентичная семантике интерпретатора.
V>- все данные строго ссылочные, исходные лисповые тела ф-ий хранятся в явном виде имогут быть исполнены через eval в текущем контексте.

Не хранятся. Пока жи ка мне как мне получить тело функции в CL.

V>И напротив, в Схеме нет оперирования текущим контекстом, нет интерпретатора тел функций, т.е. нет и Схем-машинки (кроме сбоку-припёку, которая опциональна для нужд eval БЕЗ связи с твоей программой, т.е. не нужна в общем случае).


Это зависит сугубо от реализации.

V>В общем, бета-эта-редукции в Схеме выполняются аж бегом компилятором.


Или не выполняются, если компилятора нет.

V>Когда не требуется явно оперировать контекстом исполнения и символы стираются (т.к. область видимости статическая, т.е. символы ресолвятся на этапе компиляции, а не в процессе работы) — у компилятора развязываются руки.


V>Становится не обязательным в большиснстве случаев представлять данные как связанные cons — можно проводить escape-анализ и удалять ссылочную семантику (для банальных чисел) и т.д. до бесконечности.


При чём тут cons-ячейки? И какое отношение это имеет к ссылочной семантике?

_>>Вот смотрю, например, R5RS и не вижу упоминания библиотек.

_>>Вот R7RS, eval наместе. Про библиотеку не сказано.

V>Возможно. Не принципиально.

V>Я смотрел несколько реализаций Схем, когда писал свою реализацию для дотнета, видел что eval был оформлен в отдельную либу.

А всё остальное было в одном жирном файле с кодом?

V>>>Идея Схемы была дать возможность исполнять конечный код без Лисп-машинки, без ссылочной семантики в >90% случаев, без динамического построения текущего контекста и поиска в нём символов (даже в компиллируемом Лиспе символы ищутся в текущем контексте динамически в процессе работы программы) и т.д.

_>>Откуда ты взял эту идею? У cons-ячеек в Схеме ссылочная семантика и это используется в SICP.

V>Это исходная семантика ссылочная, а компилятор вправе делать преобразования над данными.

При чём тут компилятор?

V>Например, зачем тут ссылочная семантика (sin (+ x 5)), если компилятор видит область действия x целиком, т.е. знает, что оно не убегает.

Затем, например, что x может быть как int'ом, а может — bignum и известно это будет только в рантайме. Сколько место на стэке для вызова + выделит компилятор под x?

V>>>Scheme стал первым диалектом Лиспа, применяющим исключительно статические (а не динамические) области видимости переменных

_>>Это не говорит о цели разработки. И к компиляции/интерпретации отношения не имеет.

V>

V>Статическая область видимости символов позволяет отказаться от хранения их имён в скомпиллированном образе, т.к. все символы ресолвятся еще на стадии компиляции.
V>А значит, не нужен пресловутый лисповый контекст, в котором хрен ты так просто вызовешь некую функцию fn, сначала Лисп-машинка найдёт в этом контексте текущее (т.е. ближайшее) определение символа fn, убедится, что это функция, и подаст ей хвост выражения.

Именно так и работает интерпретатор Схемы в SICP.

V>>>Что исключало необходимость в оперировании динамическим контекстом, позволило производить оптимизацию бинарного представления как в "обычных языках", например, оптимизацию хвостовой рекурсии. Одним словом, Схема дала ср-ва для стирания символов в конечном образе, хотя эти символы живут на этапе компиляции, скажем, в макросах.

_>>SBCL умеет в оптимизацию хвостовой рекурсии.

V>В условиях динамического контекста Лиспа я бы не говорил "гоп" без тщательных экспериментов — какие именно случаи поддаются раскрутке.


Ну так поэкспериментируй. Заодно расскажешь, как это Racket работает с динамическим контекстом и оптимизацией хвостовых вызовов.

V>Наверно такое, что интерпретатор Лиспа — это дешевая в разработке хрень (несколько вечеров на коленке достаточно)


Ты когда последний раз стандарт Common Lisp открывал?

V>а компилятор Схемы — дорогая разработка.

V>Соотв., стандартизация нужна, чтобы дорогой труд не пропадал даром.

никакой стандарт не мотивирован таким образом.

V>>>Например, если эмулировать ООП на Си, то таблицу виртуальных ф-ий и механику диспетчеризацию по ней (с условием корректировки указателя базы для множественного наследования, например) надо описывать явно в такой эмуляции.

_>>Но при использовании CLOS этого не нужно описывать явно. Всё описано в библиотеке.

V>Это и есть явно, т.е. на библиотечном уровне, а не ср-вами языка.


И что? Чем это мешает?

V>В С++ устройство объекта неизвестно, механика диспетчеризации виртуальных ф-ий неизвестна, механика множественного и виртуального множественного наследования неизвестна.

V>Мы можем знать только реверс-инжинирив конкретные реализации (ну и обладая эрудицией о том, как это вобще реализуется), но с т.з. языка устройство объекта неизвестно.

И что?

_>>И какая разница, находится это в библиотеке или зашито в рантайм языка?


V>Говорил уже — компилятор не может породить невалидный лейаут объекта в памяти.


Вообще может.

V>А к тебе в библиотеку запросто можно подать невалидный лейаут объекта.


Каким образом? Продемонстрируй.

_>>Какие гарантии целостности теряются в CLOS?


V>Их там просто нет.


С чего это нет?

V>Ты можешь случайным образом подать методу одного объекта инстанс другого.


В CLOS методы не принадлежат объектам. Ты и про CLOS, похоже, ничего не знаешь.

И представь себе, во всём ООП так происходит: методу объекта базового класса можно «подать» инстанс любого наследника.

V>>>В этом абзаце имелись ввиду не встроенные объекты-символы Лиспа (числа, ф-ии и CONS), а эмулируемые "объекты" в парадигме ООП.

_>>И что с того? Следует ли тогда стремиться вообще всё известное впихнуть в рантайм языка?

V>И в компилятор, и в рантайм.


Супер. Запихни youtube в рантайм.

_>>При чём тут C и C++, когда речь шла о Лиспе?


V>В том абзаце ты возражал против моего понимания первоклассной сущности.

V>ООП-объекты в Лиспе не являются первоклассными.

Является. Ты так ничего и не понял.

_>>Кроме того непонятно, что автор имел в виду под «созданием функций во время выполнения программы».


V>Лямбды, карринг.


Это такие же предкомпилированные функциональные объекткы и указатели на функции, как и в C++.

_>>Что мешает подключить интерпретатор С в программу?


V>Зачем интерпретатор? ))


V>В С++ можно описывать функциональные объекты — функторы.

V>Но они не являются первоклассными сущностями-функциями, они являются первоклассными сущностями-объектами, у которых определён метод 'operator()'.

Ты демагогией занимаешься. Функторы в C++ — first-class citizen.
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 18:18
Оценка:
Здравствуйте, vdimas, Вы писали:

V>Ты можешь случайным образом подать методу одного объекта инстанс другого.


А в SmallTalk'е ООП первоклассное? Там нельзя вызвать любой произвольный метод у любого объекта? Ошибка компиляции будет?
Re[15]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 18:32
Оценка:
Здравствуйте, vdimas, Вы писали:

_>>Какие гарантии целостности теряются в CLOS?


V>Ты можешь случайным образом подать методу одного объекта инстанс другого.


Это не отвечает на вопрос, какие гарантии целостности в этом случае нарушатся.

Если я в Схеме функции + передам не число, это нарушит гарантии целостности?
Re[14]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 19:19
Оценка:
Здравствуйте, Sinclair, Вы писали:
S> ImmutableValue(T && value) : value_(value) {}
S>Хм. А там точно будет ссылка на оригинал, а не дубликат?

Там будет и не ссылка, и не дубликат.


S>А если "исправить" ваш код так:

S> const T& value_;

Тогда мы храним ссылку, а не значение, т.е. теряем контроль над другими потенциальными ссылками на это значение.
Я ж не просто так храню именно значение. ))

Семантика перемещения активно использовалась в С++ еще до поддержки её в стандарте C++11, это популярный паттерн проектирования для плюсов, особенно в сценарии read-modify-write.
Соответственно, однажды поддержали в синтаксисе языка.

До этого выкручивались через swap, либо расписывали перемещающую семантику ручками.
swap изначально был реализован с перемещающей семантикой, например:
std::vector<int> v1;
std::vector<int> v2;
std::swap(v1, v2);

в процессе обмена содержимым переменных копии данных не создаются, вектора поэлементно не копируются.
Происходит обмен 3-х указателей: begin_, end_, capacity_end_;


S>Покажите мне пример кода, в котором вы изготавливаете из мутабельного объекта иммутабельный, и продемонстрируйте, что вы после этого не можете этот иммутабельный изменить.


template<typename T>
class ImmutableValue {
    const T value_;
public:
    ImmutableValue(T && value) : value_(move(value)) {}
    const T & value() const { return value_; }
};

int main()
{
    using namespace std;
    typedef ImmutableValue<string> istring;

    string s1 = "it is a long enough string";
    istring s2 = move(s1);
    assert(s2.value() == "it is a long enough string");
    assert(s1 == "");

    s1 = "42";
    assert(s2.value() == "it is a long enough string");
    assert(s1 == "42");

    return 0;
}


Здесь "it is a long enough string/0" выделяется в динамической памяти, затем через std::move перемещается в иммутабельную строку s2, при этом строка s1 теряет своё содержимое.
Далее мы изменяем значение строки s1, в то время как зачение s2 не изменяется.
Изменить законными способами значение переменной s2 нельзя;

    istring s3 = s2; // создание копии строки
    assert(s3.value() == "it is a long enough string");
    assert(s2.value().data() != s3.value().data()); // именно копии, по разным адресам

    s2 = s3; // ошибка компиляции: operator=(const ImmutableValue<std::string> &) удалён


Последняя строка тоже важна для понимания — получив однажды ссылку на ImmutableValue<std::string> я могу быть уверен, что переменную не затрут.


V>>Далее в своём коде используешь шаблонный ImmutableMap.

S>Вот прямо так сразу использовать шаблонный ImmutableMap не получится. Вы, как водится, привели тот самый фрагмент кода, который был очевиден и без обсуждения.
S>Как вы реализуете метод add(const TKey & key, const TValue & value)?

Никак.
Для иммутабельных типов это будут внешние ф-ии и операторы, разумеется.
Просто в дотнете сплошные методы. ))


S>Внезапно окажется, что нельзя просто взять произвольный mutable тип, завернуть его в ImmutableValue и наслаждацца.


Можно.


S>Даже если вы сможете реализовать своё обещание с "zero-overhead" конструктором перемещения (в чём я почему-то сомневаюсь)


Зря сомневаешься.


S>реализация изменяющих методов "в лоб" потребует от вас реализовать copy-on-write, что убъёт производительность.


Это смотря, какова природа/структура и объемы данных.
copy-on-write для небольших объемов линейно-расположенных в памяти данных работает лучше иммутабельного графа.
У меня выходило что-то до 2-3-х десятков машинных слов выгодней было создавать линейные копии.

Но это мы уже ответвились в другую область — в область алгоритмов и структур данных, а они зависят от конкретных задач (или классов задач, бо практически все задачи уже известны, бгг).


S>Чтобы ваш "мутабельно-иммутабельный" словарь можно было применять в реальной жизни, придётся особенным образом проектировать его мутабельную версию.


В моём случае не придётся для сценария, скажем, для которого разработали FrozenDictionary в 8-м дотнете.

Я имено сегодня прошёлся по нововведениям 8-й версии и малость поржал, бо там описали аккурат предложенный мною сценарий.
http://www.rsdn.org/forum/flame.comp/8494901.1
"У дураков мысли сходятся" (С)


S>Посмотрите для примера в код классов ImmutableXXX.Builder всё в том же неймспейсе.


Смотрел сразу же по выходу.
Не любопытно.
И странно, что оно не было сделано еще лет 20 назад, когда тема иммутабельных деревьев была модной на слуху.

Разумеется, я и сам не раз использовал аналогичные типы данных, например, в утилите описания лексических анализаторов, там узлы графов состояний НКА и ДКА.
Т.е., эта техника была отшлифована в ФП еще черти когда.

Тот же Вольфхаунд в середине нулевых показывал соотв реализации на дотнете, когда баловался с красно-чёрными иммутабельными деревьями.


V>>В С++ ключевое слово const применимо так же к полям структур/классов.

V>>Такие поля могут быть инициализированы только в конструкторе.
V>>Это аналог readonly в C#.
S>Это всё понятно.

Всё это обсуждение подзатянулось из-за того, что это было непонятно тебе.
В C# ключевое слово const у мемберов объявляет аналог статического readonly поля объекта.

В С++ это аналог readonly во всех случаях.
В случае статической инициализации поля, если компилятор видит эту инициализацию, он сразу подставляет видимое значение.


S>Да, слово const есть, можно применять его к мемберам. Счастье-то в чём?


Тебе нужны были железобетонные гарантии — ну и вот.
Если же речь об алгоритмах и структурах данных — ну так пиши эти структуры и алгоритмы над ними.
Весь механизм для этого есть.


V>>Это не столько про саму иммутабельность, сколько про специальные алгоритмы на иммутабельных графах и списках.

S>Чегось? Какие ещё алгоритмы? Какие графы? Там ровно то, что написано в названии неймспейса — реализация иммутабельного списка, словаря, множества, массива, стека, очереди, и ещё пары классов.

Проснись, коллега. ))
Это именно реализация некоторых иммутабельных алгоритмов над некоторыми иммутабельными структурами данных.

Collections.Immutable не приносит в язык иммутабельность как таковую, эта либа лишь реализует некоторые структуры и алгоритмы, доказавшие свою полезность в ФП.


V>>Соответственно, ценностью этого раздела библиотеки является уже готовая функциональность, а не какие-то там гарантии.

V>>(никаких гарантий та система типов не даёт)
S>Конечно даёт.

Не-а.
Я могу нарушать гарантии интерфейсов, например, реализовывать тип как мутабельный и возвращать this из методов.


S>Вот мой метод:

S>
S>public void RegisterState(ImmutableDictionary<string, int> state)
S>{
S>  _visited[state] = true;
S>}
S>

S>Попробуйте "сломать" его, передав в него мутабельный state.

У тебя ошибка, должно быть так:
_visited = _visited.SetItem(state, true);

Возвращается ссылка на другой экземпляр.
(который в общем случае включает узлы исходного дерева)

И это у тебя оно гарантируется из-за использования конкретного типа ImmutableDictionary<,>, а если сделать так:
public void RegisterState<TDict, TKey, TValue>(TDict state)
    where TDict : IImmutableDictionary<TKey, TValue> {...}

то прощай гарантии, можно начинать хулиганить. ))

В плюсах, напротив, можно определить некий концепт ImmutableMap и юзать так:
template<ImmutableMap TDict>
void RegisterState(TDict state) {
   visited_ = merge(visited_, state, true);
}

Где концепт ImmutableMap требует, например, быть инстансом шаблонного типа ImmutableMapWrapper, которому в кач-ве аргумента подали тип, удовлетворяющий публичному интерфейсу std::map.

Что касается ключевой фишки иммутабельных списков/графов, т.е. что касается шаренья узлов между инстансами, этого можно достичь или через счётчики ссылок, или через техники навроде региональной памяти, чтобы не возиться со счётчиками.
Отредактировано 28.03.2023 19:29 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 19:29 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 19:24 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 19:23 vdimas . Предыдущая версия .
Re[16]: понимание ООП Алана Кея
От: vdimas Россия  
Дата: 28.03.23 19:53
Оценка:
Здравствуйте, korvin_, Вы писали:

V>>Expression<T> и его наследники не являются first class citizen.

V>>С т.з. языка это "пользовательские типы данных".
V>>В общем, требование first class citizen надуманное.
_>First-class citizen и built-in — ортогональные вещи.
_>First-class citizen — это:
_>

_>Robin Popplestone gave the following definition: All items have certain fundamental rights.
_>All items can be the actual parameters of functions
_>All items can be returned as results of functions
_>All items can be the subject of assignment statements
_>All items can be tested for equality.

_>последний пункт неоднозначный, но «пользовательские/встроенные типы данных» тут совершенно не при чём.

Что показывает, что ретроспектива развития IT, об которую мы тут зацепились, очень даже на пользу. ))

Теперь на пальцах.
Во времена, когда этот мем появился, чаще нельзя было передавать пользовательские типы данных по значению.

Например, в языке Си:
typedef struct {
    int i, j;
} S;

S func1(S arg); // ошибка компиляции


Но можно передать по указателю:
typedef struct {
    int i, j;
} S;

void func1(const S * arg, S * result); // OK

Соответственно, структура S не являлась первоклассной сущностью, а указатель являлся.

Первоклассные сущности имели неизвестную структуру, именно поэтому вопросы передачи и возврата таких значений тогдашние компиляторы были вынуждены брать на себя.
"Ортогональность" появилась позже, по мере развития ЯВУ, но тогда и определение надо было доработать.

И да, на твоё замечание я уже отвечал рядом:

V>>Expression<T> и его наследники не являются first class citizen.
НС>В языке C# — являются.

Только в том смысле, что наследуются от Object, который first class citizen.
Так можно про любой GC-тип дотнета сказать. ))

Строго говоря, первоклассной сущностью в C# являются ссылки на объекты, но не сами объекты, внезапно.
Ну и, я считаю объекты Object, Delegate, String, Array тоже первоклассными, помимо примитивных типов.

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

Плюс целый ряд допущений/ограничений был сделан для value-type, чтобы сделать его первоклассной сущностью, а именно: дефолтная инициализация памяти структуры байтами-нулями, дефолтное побитовое копирование содержимого структур, невозможность переопределения перечисленного.

В общем, Expression<T> не являются отдельной первоклассной сущностью AST, по крайней мере не большей, чем некий MyExpression<T>, то бишь никакого уникального признака "first citizen" у них нет. И даже не являются встроенными типами неизвестной структуры. Это просто вынужденный костыль, бо разработка Roslyn была только в проекте.

Для сравнение, результат typeof(T) в дотнете можно с некоторой натяжкой назвать отдельной первоклассной сущностью.
Хотя тип Type и описан как "обычный объект", но это просто обертка (Decorator) к типу неизвестной (непрозрачной) структуры, представляющей из себя бинарные кишки платформы.
Такими непрозрачными типами являются TypeHandle, GCHandle и т.д.

Напротив, иерархия Expression<T> строится на абсолютно прозрачных типах юзверского уровня, и определение этих типов, разумеется, даётся в библиотечном виде.
Отредактировано 28.03.2023 20:08 vdimas . Предыдущая версия . Еще …
Отредактировано 28.03.2023 20:05 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 20:02 vdimas . Предыдущая версия .
Отредактировано 28.03.2023 20:00 vdimas . Предыдущая версия .
Re[17]: понимание ООП Алана Кея
От: korvin_  
Дата: 28.03.23 20:48
Оценка: +2 :)
Здравствуйте, vdimas, Вы писали:

V>Во времена, когда этот мем появился, чаще нельзя было передавать пользовательские типы данных по значению.


При чём тут мемы?

V>Например, в языке Си:

V>
V>typedef struct {
V>    int i, j;
V>} S;

V>S func1(S arg); // ошибка компиляции
V>


V>Но можно передать по указателю:

V>
V>typedef struct {
V>    int i, j;
V>} S;

V>void func1(const S * arg, S * result); // OK
V>

V>Соответственно, структура S не являлась первоклассной сущностью, а указатель являлся.

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


Какая неизвестная структура у указателя?

V>"Ортогональность" появилась позже, по мере развития ЯВУ, но тогда и определение надо было доработать.


Определение и доработали, я приводил цитату из 4-х пунктов.

V>И да, на твоё замечание я уже отвечал рядом:

V>

V>>>Expression<T> и его наследники не являются first class citizen.
НС>>В языке C# — являются.

V>Только в том смысле, что наследуются от Object, который first class citizen.
V>Так можно про любой GC-тип дотнета сказать. ))

V>Строго говоря, первоклассной сущностью в C# являются ссылки на объекты, но не сами объекты, внезапно.

Строго говоря, это словоблудие. Косвенность сегодня никого особо не волнует.

V>В общем, Expression<T> не являются отдельной первоклассной сущностью AST, по крайней мере не большей, чем некий MyExpression<T>, то бишь никакого уникального признака "first citizen" у них нет. И даже не являются встроенными типами неизвестной структуры. Это просто вынужденный костыль, бо разработка Roslyn была только в проекте.


V>Для сравнение, результат typeof(T) в дотнете можно с некоторой натяжкой назвать отдельной первоклассной сущностью.

V>Хотя тип Type и описан как "обычный объект", но это просто обертка (Decorator) к типу неизвестной (непрозрачной) структуры, представляющей из себя бинарные кишки платформы.
V>Такими непрозрачными типами являются TypeHandle, GCHandle и т.д.



V>Напротив, иерархия Expression<T> строится на абсолютно прозрачных типах юзверского уровня, и определение этих типов, разумеется, даётся в библиотечном виде.


Ещё раз: «прозрачность» не имеет никакого значения для определения, что является first-class citizen, а что — нет.

P. S. Дополнение к примеру syntax-case:

(library (syntax-utils)

  (export syntax-car
          syntax-cdr
          syntax-cadr
          syntax-null?
          ids?
          unique-ids?)
  (import (rnrs))

  (define (syntax-car x)
    (syntax-case x ()
      ((car . cdr) #'car)))

  (define ids?
    (lambda (ls)
      (or (null? ls)
          (and (identifier? (car ls)) (ids? (cdr ls))))))
    
  (define unique-ids?
    (lambda (ls)
      (or (null? ls)
          (and (not (memp
                     (lambda (x) (bound-identifier=? x (car ls)))
                     (cdr ls)))
               (unique-ids? (cdr ls))))))
    
  (define (syntax-cdr x)
    (syntax-case x ()
      ((car . cdr) #'cdr)))

  (define (syntax-cadr x)
    (syntax-car (syntax-cdr x)))
    
  (define (syntax-null? x)
    (syntax-case x ()
      (()          #t)
      ((car . cdr) #f)))

  'end)

...
(import (rnrs)
        (for (r6rs-ext syntax-utils) expand))

(define-syntax let
  (lambda (x)
    (syntax-case x ()
      ((_ . body)
       (if (syntax-null? (syntax-cdr #'body))
           (error 'let "invalid syntax")
           (let loop ((vars    '())
                      (exprs   '())
                      (bindings (syntax-car #'body)))
             (cond ((not (syntax-null? bindings))
                    (let* ((binding (syntax-car bindings))
                           (var  (syntax-car  binding))
                           (expr (syntax-cadr binding)))
                      (loop (cons var   vars)
                            (cons expr  exprs)
                            (syntax-cdr bindings))))
                   ((and (ids? vars) (unique-ids? vars))
                    #`((lambda #,(reverse vars) #,@(syntax-cdr #'body)) #,@(reverse exprs)))
                   (else
                    (error 'let "invalid syntax")))))))))


(let ((x 1)
      (y 2))
  (display (+ x y))
  (newline))

— всё, никаких шаблонов. Да и syntax-case можно убрать вообще, убрав даже (_ . body)
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.