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.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.