Здравствуйте, 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.