Информация об изменениях

Сообщение Re[14]: понимание ООП Алана Кея от 28.03.2023 17:01

Изменено 28.03.2023 17:02 vdimas

Re[14]: понимание ООП Алана Кея
Здравствуйте, 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()'.
Re[14]: понимание ООП Алана Кея
Здравствуйте, 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()'.