Решил всё таки посмотреть ПФП (я смотрел уже, но мало) :) Перво-наперво возникли мысли о ленивости, точнее о отношении ленивости в Haskell (всё никак не пойму этот язык) и макросов в CL.
Интерлюдия: вот thesz пишет, что некоторая ленивость в "неблагородных" языка возможна благодаря специальным формам. В CL есть средство для определения пользовательских специальных форм - макросы. Таким образом, каждый раз определяя макрос мы, между делом, определяем какую-либо стратегию ленивости (если так можно выразиться). Конечно, между макросами и ленивостью больше различий, но некоторое сходство имеется.
Ещё
в этом посте человек пишет о том, что некоторые свойства макросов можно получить в Haskell с помощью (разумеется) ленивых функций, потом Peter Seibel убеждает его, что макросы гораздо более мощны во всех отношениях. И все с ним соглашаются :)
В общем,
максима: макросами можно эмулировать ленивостью, но ленивость во многих отношениях не заменит макросов.
Пример с оператором &&:
False && x
= False
True && x
= x
Эквивалентен следующему макросу:
(defmacro && (x y)
(if x y nil))(&& t (progn (print 'energy?) nil))
(&& nil (progn (print 'energy?) nil)) В данном случае вычисление происходит вполне ленивым образом - лишних движений не производится.
Что касается сопоставления с образцом, то его энергичный вариант уже реализован в макросах CLOS:
(defgeneric && (x y)
(:method ((x (eql t)) y) y)
(:method ( x y) nil))или, более базово:
(defmethod && ((x (eql t)) y) y)
(defmethod && ( x y) nil))
возможено как сопоставление по типам и классам (полиморфизм), так и по значениям.
(вот в этом месте я бы должен написал крутай макрос для ленивого-паттерн матчинга с масками и клозами... Но главное, что это возможно, и достаточно не сложно.)
Ещё один подобный пример:
data BoolU = FALSE | TRUE | Unknown
FALSE && x
= FALSE
TRUE && x
= x
Unknown && FALSE = FALSE
Unknown && x
= UnknownИ ему соответствует макрос:
(defmacro && (x y)
(ecase x
(:false :false)
(:true y)
(:unknown (ecase y
(:false :false)
(:true :unknown)
(:unknown :unknown)))))Такие макросы работают настолько лениво, насколько это требуется. При этом всегда есть возможность смешивать в рамках одного макроса ленивый и энергичный подходы - что-то принудительно вычислять, а что-то наоборот, оставлять на будущее (до стадии eval).
Единственное преимущество Haskell с точки зрения ленивости в том, что под категорию ленивости попадают некоторые оптимизации, которые может производить компиятор языка, построенного вокруг соглашения о вызове по надобности. К примеру, следующий код на CL компилируется не лучшим образом:
(let ((n (factorial (random 1 1000))))
(if condition
n
nil)) или даже
(let ((n (factorial 1000)))
(if nil
n
nil)) Конечно, можно придумать специальную оптимизацию, но в изначально-ленивом языке нужная оптимизация производится автоматически.
Просто прочитай SICP, там всё хорошо расписано про ленивость.
Ну в SICP строится ленивый транслятор :) и упоминается, что специальные формы на то и специальные, что не укладываются в обычную подстановочную модель (`с` или `без` окружения, неважно), а могут действовать, например, лениво.
Про макры там ничего нет. Вот, а макрос это такая ручная специальная форма ленивость/энергичность которой можно регулировать. Это такая ассоциация, точного мат. обоснования у меня нет :)
макросы и ленивость это разные вещи.
возможно где-то они пересекаются -
например при вычислении аргументов.
хороший пример ленивости: открыть
гигабайтный файл, найти в нем все вхождения
слова macro и в результате отдать список из
позиций в этом файле всех вхождений этого слова.
так вот если потом распечатать только первые 5
элементов из этого списка, то поиск будет вестись
только первых 5 вхождений.
Если в языке нет ленивости, то был бы найден
весь список(т.е. пришлось бы прочитать весь файл).
> возможно где-то они пересекаются
А мне кажется, макросы отличаются от ленивых функций только тем, что предоставляют контроль над вычислением аргументов (чем они и гибче) ну и тем, что подставляются в код, что можно обойти, используя связку eval - macroexpand, где нужно ленивое вычисление в рантайме, а не компайл-тайме.
h1t, в таком случае ко-данные (потенциально бесконечные структуры данных, как в вашем примере) и ленивость это тоже разные вещи :)
Какая вообще может быть связь между ленивостью и макросами я не уловил. И да, в CL ленивости нет.
> Какая вообще может быть связь между ленивостью и макросами я не уловил.
Ленивость функции - свойство откладывать вычисление фактических параметров до тех пор, пока оно не пригодилось. Макросы в common-lisp не вычисляют свои аргументы и позволяют контролировать их вычисление. Вот и связь :)
> И да, в CL ленивости нет.
Ну нету стандартных средств, но реализовать можно.
Пример:
(defun foo-a (arg)
(foo-b arg))
(defun foo-b (arg)
(+ 1 2 3))
(defmacro lazy-foo-a (arg)
(lazy-foo-b arg))
(defmacro lazy-foo-b (arg)
(+ 1 2 3))
(defmacro lazy-foo-c (arg)
(lazy-foo-b arg))
(defmacro lazy-foo-d (arg)
(+ 1 2 (eval arg)))
Ну или вот так совсем наглядно:
(defmacro lazy-foo-c (lazy-funarg arg)
(macroexpand `(,lazy-funarg ,arg)))
(defmacro lazy-foo-d (arg)
(+ 1 2 (eval arg)))
(defmacro lazy-foo-e (arg)
(+ 1 2 3))
> Макросы в common-lisp не вычисляют свои аргументы и позволяют контролировать их вычисление.
Не так, макрос это просто функция, которая принимает набор аргументов, в том числе возможно кусок кода, и преобразует его в код, который подставляется в место вызова макроса на этапе компиляции, при этом возможны различные сайд-эффекты.
Ленивые вычисления неразрывно связаны с декомпозицией на основе потоков, а макросы ни с какой определённой моделью декомпозиции не связаны, это просто средство для борьбы с дублированием кода.
Ну а пример с eval это просто пример с eval, ленивость тут не при чём. С таким же успехом можно было связать с ленивостью декораторы в Python, или скажем замыкания в JavaScript.
> И да, в CL ленивости нет.
А если на время придумать некую define-lazy-function для определения ленивой функции, исчисляющейся по правилу lazy-eval; и посмотреть на её свойства, - можно будет заметить, что это не что иное как defmacro.
Базовая ленивость по отношению к аргументам (бесконечные данные не берём) - подмножество макро-системы.
> макрос это просто функция, которая принимает набор аргументов, в том
числе возможно кусок кода, и преобразует его в код, который
подставляется в место вызова макроса на этапе компиляции.
Макрос "подставляет куски кода не вычисляя их" и ленивая функция "осуществляет call-by-name для аргументов не вычисляя их". Как-то похоже?
>> Базовая ленивость по отношению к аргументам (бесконечные данные не берём) - подмножество макро-системы.
Хотя, бред, конечно :)
> Макрос "подставляет куски кода не вычисляя их" и ленивая функция "осуществляет call-by-name для аргументов не вычисляя их". > Как-то похоже?
Нет, не похоже. Что значит "не вычисляя"? Макрос раскрывается на этапе компиляции. И он вычисляет свои аргументы, просто это "аргументы времени компиляции", гы :)
Методы использования ленивых вычислений и макросов вообще ничего общего между собой не имеют, о чём тут можно вообще говорить?
> Ленивые вычисления неразрывно связаны с декомпозицией на основе потоков
Потоки - это просто трюк, который возможен благодаря ленивому вычислению. И ленивые вычисления - понятие пошире, чем ленивые функции.
> который подставляется в место вызова макроса на этапе компиляции
Этап компиляции в лиспе можно организовать в любой момент рантайма, что и позволяет использовать макросы в качестве ленивых функций. Вот пример:
(defun i-am-func-that-evaluates-macro-in-runtime-doin-it-kinda-lazy-func (lazy-funarg arg)
(macroexpand `(,lazy-funarg ,arg)))
(i-am-func-that-evaluates-macro-in-runtime-doin-it-kinda-lazy-func 'lazy-foo-d '(+ 7 8))
:> 18
Цитата из Харрисона-Филда:
В языке, подобном Паскалю, при применении функции к аргументу последний сначала вычисляется, а затем уже передаётся функции. В этом случае мы говорим, что аргумент передаётся по значению, подразумевая при этом, что только его значение передаётся в тело функции. Такое правило вычислений или механизм вызова называется вызовом по значению. Преимущество вызова по значению заключается в том, что эффективная реализация проста: сначала вычисляется аргумент, а затем вызывается функция. Недостатком является избыточное вычисление, когда значение аргумента не требуется вызываемой функции. Альтернативой вызову по значению является вызов по необходимости, в котором все аргументы передаются функции в невычисленном виде и вычисляются только тогда, когда в них возникает необходимость внутри тела функции. Преимущество этого вызова состоит в том, что никакие затраты не пропадут попусту в случае, если значение аргумента в конце концов не понадобится. А недостаток - в том, что по сравнению с вызовом по значению вызов по необходимости является более дорогим, поскольку функциям передаются не значения тех или иных параметров, а невычисленные выражения.В контексте функциональных языков можно говорить о двух видах вычисления, энергичном и ленивом, хотя существуют и другие варианты. Принцип энергичного вычисления - "делай всё, что можешь". Другими словами - не надо заботиться о том, пригодится ли в конечном результате полученный результат. Принцип ленивого вычисления - "не делай ничего, пока этого не потребуется". В терминах традиционного программирования энергичное вычисление можно приблизительно соотнести с механизмом вызова по значению, а ленивое - с механизмом вызова по необходимости.Затем, в главе посвященной лямбда исчислению, даётся совсем строгое определение:
Ленивое вычисление = НПР, приводящий выражение с СЗНФ + разделение + ленивые конструкторыили эквивалентно:Ленивое вычисление = вызов по необходимости + ленивые конструкторыЯ считаю, что конкретно-функции достаточно удовлетворять соглашению о вызове-по-необходимости, чтобы считаться ленивой.
> Этап компиляции в лиспе можно организовать в любой момент рантайма
Блин, давно пора написать какую-то книгу по этому поводу и назвать её книгой #1 для прочтения тем, кто планирует изучать лисп. Столько уже путаницы было и еще будет из-за этого разделения compile-time / runtime, которого в лиспе на самом деле вообще нет.
>> Методы использования ленивых вычислений и макросов вообще ничего общего между собой не имеют, о чём тут можно вообще говорить?
Если я сделаю макрос if или макрос && или ещё что-то вроде этого, то я реализую ленивую специальную форму (подобную функции в хаскеле):
if - не будет вычислять одну из ветвей.
&& - может опустить вычисление своего второго аргумента.
и т.д.
В рамках модели call-by-value всё всегда вычисляется.
А если это не call-by-value, то что ?
> Потоки - это просто трюк
Гы... За всю историю программирования было изобретено всего два подхода к декомпозиции: объектная (не путать с ООП) и декомпозиция на основе потоков. Так что, это не трюк, а фундаментальная концепция.
> Этап компиляции в лиспе можно организовать в любой момент рантайма,
> что и позволяет использовать макросы в качестве ленивых функций.
В CL компиляция это процесс раскрытия макросов. Это не то же самое, что в C++ или Haskell.
> В контексте функциональных языков можно говорить о двух видах вычисления,
> энергичном и ленивом, хотя существуют и другие варианты.
В данном контексте говорить о CL как о ФЯП точно нельзя, это обычный, "традиционный" язык программирования. Макросы это простые функции, аргументами которых являются s-выражения и выполняющиеся на этапе копиляции, никаких "ленивых" вычислений.
> Я считаю, что конкретно-функции достаточно удовлетворять соглашению
> о вызове-по-необходимости, чтобы считаться ленивой.
В CL таких функций нет. В CL нет ленивых конструкторов, в CL то и конструкторов нет.
Или как это понимать? Форма when превращает функцию в ленивую (есть необходимость - вычисляем, нет - не вычисляем)?
> Столько уже путаницы было и еще будет из-за этого разделения compile-time / runtime, которого в лиспе на самом деле вообще нет.
Термин compile-time используется в Hyperspec постоянно, так что очень даже есть. Просто не надо путать с аналогичными терминами из C и т.п. языков
> Если я сделаю макрос if или макрос &&
Макрос не может быть примером ленивой функции, это вообще не функция, для него нельзя сделать funcall. Это статическая конструкция (со всем оговорками).
>> макрос это просто функция
>> Макрос ... это вообще не функция
Ну так что это ?)) По мне так это полноправный элемент computation, такой же как и функция, только другого порядка - трансформатор, ну и ленивая (опционально) специальная форма как частный случай трансформатора.
>> макрос это просто функция
>> Макрос ... это вообще не функция
>>> Ну так что это ?))
С макросом связана (macro-function ...), которая является самой обыкновенной функций. Но это совсем не тоже самое, что (symbol-function ...)
>> Макросы это простые функции, аргументами которых являются
s-выражения и выполняющиеся на этапе копиляции, никаких "ленивых"
вычислений.
Согласен. Именно ленивых _вычислений_ нет. О чём весь сыр-бор? Я же говорил только о ленивых спец. формах и даже там слово "эмулировать" поставил - некоторые (не знаю насколько широкий класс) ленивые функции можно эмулировать макросами. Вот и всё.
> Я же говорил только о ленивых спец. формах и даже там слово "эмулировать" поставил - > некоторые (не знаю насколько широкий класс) ленивые функции можно эмулировать макросами.
Код в студию.
Или нужно что-то убойное ?
> Первый пост?
Там нет ленивых вычислений.
>> Там нет ленивых вычислений.
Есть же - в &&
Опять же - эмуляция. Ну внутренне устройство (что там? потоки, ещё какие-то санки?), а интерфейс и эффект. Интерфейс - специальная форма &&, эффект - задержка (остановка) вычисления не нужных форм.
> эффект - задержка (остановка) вычисления не нужных форм.
Ну тогда даже макросы в языке C тоже позволяют производить ленивые вычисления, ты это хотел сказать? Эх, а какие возможности "ленивых вычислений" предоставляют шаблоны С++, ух!
И чего с ними все возятся, если они есть везде? непонятно...
В си - некий препроцессор, как реализованы шаблоны я не знаю. Формально, я говорю о тех штуках, которые позволяют пересобирать AST, и даже что-то знают о семантике - мета средства в C/C++ тут не сравнимы по возможностям.
> Формально, я говорю о тех штуках, которые позволяют пересобирать AST
Я думал, мы говорим о ленивости. При чём тут AST?
> мета средства в C/C++ тут не сравнимы по возможностям.
Гы... Ссылки не помню, но была довольна известная работа по реализации интепретатора Lisp на основе шаблонов C++, которая работала во время компиляции ;) Так что, это ещё вопрос...
Кстати, языка С/С++ нет, если разговор о шаблонах, то нужно писать только C++ ...
К слову, о первом посте, && в си - тоже ленивый.
> Форма when превращает функцию в ленивую (есть необходимость - вычисляем, нет - не вычисляем)?
Ничего она не превращает. Она сама является ленивой функцией, вычисляющей все свои аргументы только при необходимости.
> Ну тогда даже макросы в языке C тоже позволяют производить ленивые
вычисления, ты это хотел сказать? Эх, а какие возможности "ленивых
вычислений" предоставляют шаблоны С++, ух!
Раскрытие "макросов" в си, и шаблонов в си++ происходит только один
раз, еще на этапе препроцессирования. В common-lisp что бы ни делалось,
всё происходит в рантайме - любая компиляция, любое объявление функции, переменной, макроса, посему объявлять макросы можно из функций, раскрывать их можно в любое время
на лету и так далее. Тот рантайм, в котором будет происходить компил
файла с кодом ничем не отличается от рантайма, который будет
существовать во время работы какой-нибудь функции.
> За всю историю программирования было изобретено всего два подхода к декомпозиции
Наиболее часто применяемые стратегии декомпозиции:
- Функциональная декомпозиция. Декомпозиция базируется на анализе
функций системы. При этом ставится вопрос, что делает система,
независимо от того, как она работает. Основанием разбиения на
функциональные подсистемы служит общность функций, выполняемых группами
элементов.
- Декомпозиция по жизненному циклу. Признак выделения
подсистем — изменение закона функционирования подсистем на разных
этапах цикла существования системы «от рождения до гибели». Для
жизненного цикла управления организационно-экономической системы выделяют этапы планирования, инициирования, координации, контроля, регулирования. Для информационных
систем разделяют этапы обработки информации: регистрацию, сбор,
передачу, обработку, отображение, хранение, защиту, уничтожение.
- Декомпозиция по физическому процессу. Признак выделения
подсистем — шаги выполнения алгоритма функционирования подсистемы,
стадии смены состояний. Хотя эта стратегия полезна при описании
существующих процессов, результатом её часто может стать слишком
последовательное описание системы, которое не будет в полной мере
учитывать ограничения, диктуемые функциями друг другу. При этом может
оказаться скрытой последовательность управления. Применять эту
стратегию следует, только если целью модели является описание
физического процесса как такового.
- Декомпозиция по подсистемам (структурная декомпозиция).
Признак выделения подсистем — сильная связь между элементами по одному
из типов отношений (связей), существующих в системе (информационных,
логических, иерархических, энергетических и т. п.). Силу связи по информации можно оценить коэффициентом информационной взаимосвязи подсистем k= N/N0, где N — количество взаимоиспользуемых информационных массивов в подсистемах, N0
— общее количество информационных массивов. Для описания всей системы
должна быть построена составная модель, объединяющая все отдельные
модели. *
- Декомпозиция по входам для организационно-экономических
систем. Признак выделения подсистем: источник воздействия на систему,
это может быть вышестоящая или нижестоящая система, а также
существенная среда.
- Декомпозиция по типам ресурсов, потребляемых системой.
Формальный перечень типов ресурсов состоит из энергии, материи, времени
и информации (для социальных систем добавляются кадры и финансы).
- Декомпозиция по конечным продуктам системы. Основанием могут служить различные виды продукта, производимые системой
> Она сама является ленивой функцией, вычисляющей все свои аргументы только при необходимости.
when это не функция, поэтому называть его "ленивой функцией" нельзя.
> Тот рантайм, в котором будет происходить компил файла с кодом ничем не отличается от рантайма, > который будет существовать во время работы какой-нибудь функции.
Как же, а eval-when зачем?
> Раскрытие "макросов" в си, и шаблонов в си++ происходит только один раз, еще на этапе препроцессирования.
"препроцессирование", это к макросам C, а не к шаблонам C++.
Тоже самое и в CL, во время компиляции макросы раскрываются один раз. И именно из-за этого возможны и имеют смысл сайд-эффекты при раскрытии макросов.
> common-lisp что бы ни делалось, всё происходит в рантайме
Да, так происходит во всех динамических языках, например, в PHP или там Lua (ну их много разных).
Т.е. ленивые вычисления есть во всех языках имеющих функцию eval? Я правильно понимаю?
"ленивые вычисления" == eval ?
> Наиболее часто применяемые стратегии декомпозиции:
Это что такое вообще ниже? Какое это имеет отношение, к тому, что говорил я? Как бы там ни было, хотите оспорить авторитет SICP?
А не проще ли написать такое определение ленивой функции: все аргументы в теле функции как значения заменить на вызов этой функции("foo" заменить на "(funcall foo)").
(deflazy bar (foo)(+ foo 10)) ;=>
(defun bar(foo) (+ (funcall foo) 10))
А вызов функции - макрос вроде
(lazy-call foo a (* b c)) => (funcall #'foo #'(lambda()a) #'(lambda()(* b c)))
Вроде это вся ленивость
> when это не функция, поэтому называть его "ленивой функцией" нельзя.
when - это макрос, а макрос - это функция, поэтому можно :)
> Как же, а eval-when зачем?
Это просто фича, позволяющая организовать время вычисления для некоторых специальных случаев, типа загрузки файла или компила топ-левевела.
> Тоже самое и в CL, во время компиляции макросы раскрываются один раз.
Во время компиляции и при принудительном макроэкспанде. При этом компиляцию можно вызвать когда угодно и где угодно, а макроэкспанд, как в приведённом примере, равносилен его раскрытию, равносильном исполнению ленивой функции, которой он семантически соответствует.
> Т.е. ленивые вычисления есть во всех языках имеющих функцию eval? Я правильно понимаю?
Суть ленивых вычислений - откладывание вычисленния некоторых данных, которые могут быть представлены по-разному, от чего зависит эффективность. В cl их представление можно организовать абсолютно как угодно - список, текст, скомпилированный нативный код. Если же делать это на статических языках - представление в лучшем случае будет байткодом, что касается и большинства динамических.
>Вроде это вся ленивость
Точнее вся что относится к вычислению/невычислению аргументов функции
Эта тема какой-то хаскель головного мозга...
P.S. Вообще, хаскелисты терминологию все пермешали и всех сбили с толку. Как говорит википедиа, "lazy evaluation is the technique of delaying a computation until the result is required." Хаскелисты оседлали эту терминологию, привнеся с собой маленькую кучу "но" вселенских маштабов. Поэтому то, что раньше нормальные люди понимали под lazy evaluation благодаря хаскелистам путаница в терминологии.
Декомпози́ция — научный метод, использующий структуру задачи и
позволяющий заменить решение одной большой задачи решением серии
меньших задач.
SICP хорошая книга, но субъективность присутствует всегда и везде. То,
что они там пишут - исключительно их мнение, продиктованное их видением декомпозиции-задач-в-программировании и больше нигде оно не
встречается, за исключением вдохновлённых ими постов/статей. Обвинять их ни в чём нельзя, потому что разговоры про разбиения задач на подзадачи по определению не могут быть строгими. Мне
кажется, лучше формировать своё представление хотя-бы на нескольких
(десятках) источниках, чтобы иметь во-первых разностороннее представление, во-вторых оценить строгость/нестрогость обсуждаемых вопросов.
to Fallen: Оно может и будет ленивым, да только очень жирным ;) Потом, подобная техника требует специального использования. Т.е. необходимость использования lazw-call фактически убивает всю затею.
to Ander Skirnir:> а макрос - это функция,
Можно посмотреть пример с funcall или apply для when?
> Это просто фичаСкорее
геморой.
> В cl их представление можно организовать абсолютно как угодно - список, текст, скомпилированный нативный код.Ничего не понял. Чем средства CL для организации "ленивых вычислений" отличаются от аналогичных возможностей PHP/Python/Perl/JavaScript/Lua/"выбери любой на свой вкус".
> Если же делать это на статических языках - представление в лучшем случае будет байткодом, что касается и большинства динамических.Опять ничего не понял. Но можно привести пример, как реализовать на CL, например,
yelid, который есть в Python или там в C# и который вполне подходит для демонстрации частного случая "ленивых вычислений"?
> То, что они там пишут - исключительно их мнение, продиктованное их видением декомпозиции-задач-в-программировании
> и больше нигде оно не встречается
Мне так казалось наоборот, и при этом, "их мнение" полностью соответствует моим наблюдениям, разные книги читали?
> Мне кажется, лучше формировать своё представление хотя-бы на нескольких (десятках) источниках
Источники это книги? гы, это точно не ко мне, мне за всю жизнь столько не осилить. Но я видел много разных программ, и везде используется объектная декомпозиция (надо же). О декомпозиции на основе потоков мне приходилось только читать (у Страуструпа и в SICP), но я не смотрел программ, написанных на функциональных языках, а применять декомпозицию на основе потоков, насколько я могу судить, сейчас имеет смысл только при использовании ФЯП.
Что жирным будет согласен.=) Но вместо lazy-call можно использовать макрос, который, к примеру, в зависимости от наличия в списке свойств свойства :lazy раскрывался бы в lazy-call или funcall или ещё что. И так со всеми своими функциями. Думаю это небольшая проблема при использовании.
> Думаю это небольшая проблема при использовании.
Проблема в том, что нельзя просто непосредственно вызвать функцию.
>Чем средства CL для организации "ленивых вычислений" отличаются от
аналогичных возможностей PHP/Python/Perl/JavaScript/Lua/"выбери любой
на свой вкус".
Тем что их использование и написание будет естественным, легким и удобным(относительно других языков).
К примеру я слабо представляю как наименее убого реализовать вышеозвученную идею. Там ведь ни макросов, ни замыканий.
> Источники это книги?
Да не, вообще источники информации, предоставляемые разными людьми.
> у Страуструпа
Ого, а где он-то где о поточной декомпозиции писал ?
> а применять декомпозицию на основе потоков, насколько я могу судить, сейчас имеет смысл только при использовании ФЯП
Ну успешное её применение на лиспах в sicp'е вполне описано. А вообще, концепция крайне простая:
(stream-cons a b) = (cons a (quote b)); (stream-cdr x) = (eval (cdr x)).
> Но можно привести пример, как реализовать на CL, например, yelid
Ну вообще, я не знаю, что может быть нагляднее тех примеров, что я уже
привёл, но могу и это попробовать сделать - только пока не знаю, как
оно работает, поэтому попозже.
>Проблема в том, что нельзя просто непосредственно вызвать функцию.
Можно, если вызывать макрос, который раскроется в функцию и lambd'енные аргументы(То бишь deflazy будет раскрываться в defmacro). Правда неприятно что функции становятся макрами. Но принципиальных ограничений не наблюдаю.
>> у Страуструпа
> Ого, а где он-то где о поточной декомпозиции писал ?
Как где? Постоянно, в "Язык программирования С++" встречаются комментарии, что пора забыть про декомпозицию на основе потоков и использовать естественный ООП. Я когда первый раз читал никак не мог понять, о каких таких потоках он постоянно говорит.
> Ну успешное её применение на лиспах в sicp'е вполне описано.
Нет, там описан костыль, которым надо пользоваться специальным образом.
> А вообще, концепция крайне простая:
Издеваешься? "(stream-cons a b) = (cons a (quote b)); (stream-cdr x) = (eval (cdr x))." назвать концепций как-то у меня не получается. К декомпозиции на основе потоков приведённый код отношения не имеет.
> Ну вообще, я не знаю, что может быть нагляднее тех примеров, что я уже привёл,
Я запомнил только использование eval, что никак к "ленивым вычислениям" отнесено быть не может.
> только пока не знаю, как оно работает
Фактически, yelid можно рассматривать как частный случай "продолжений" (сопрограмм), которые CL тоже не поддерживает. Реализовать без костылей на CL нельзя (на Scheme легко).
> eval, что никак к "ленивым вычислениям" отнесено быть не может.
Ладно, это вопрос идеологии. Я считаю довольно строгой трактовку Харрисона и Филда, а согласно ей, даже eval строки в перле, отложенный на потом, может быть организован так, что вполне будет ленивым вычислением (вернее, вычислением функции ленивым образом - с вызовом по необходимости).
> Фактически, yelid можно рассматривать как частный случай
"продолжений"
(сопрограмм), которые CL тоже не поддерживает.
Ну да, если рассматривать ленивые вычисления как отложенные вычисления,
организованные исключительно джампами, тогда позиция вполне понятна.
> Нет, там описан костыль, которым надо пользоваться специальным образом.
Ну можно dsl написать, в котором не надо будет.
> Постоянно, в "Язык программирования С++"
Когда раньше читал, не замечал, сейчас перелистал - тоже не увидел. У меня второе издание.
> Реализовать без костылей на CL нельзя
И смотря что считать костылями - необходимость писать dsl, чтобы пользоваться этими фичами естественно, или невозможость реализовать это именно так, чтобы на низком уровне оно работало наилучшим образом.
Кстати, видны две принципиально разных задачи ленивых вычислений - вычисление динамических данных при необходимости и исполнение зараннее скомпилированного кода при необходимости. Причём я изначально только первую рассматривал, потому что о ней идёт речь в определении того, что есть ленивое вычисление.
И да, ленивые конструкторы - не что иное, как (stream-cons a b) => (cons a (quote b)). НО! Это как-раз то место, где пересекаются две задачи ленивых вычислений, потому что в sicp'е её решение предлагается с точки зрения первой задачи (eval'ом кудра), а в чистых фяп она чаще решается как вторая (ко-рутинами).