2009-11-07

О страшных монадах

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

Как верно было подмечено, монады — это очень просто. А еще монады — везде. Почему же тогда у многих с ними проблемы?

1. Терминология
Где-то написано, что использование нетипичной для предметной области терминологии — это аттрибут троллинга (к сожалению, не удалось найти ссылку). Можно возразить: "да ведь монады — это же математический термин". Да, но математика и инженерия (в частности, программирование) — разные дисциплины. Математики не строят мосты, да. А термин монада не относится к предметной области программирования. В ней есть такое понятие как "вычисление" (computation). (Пусть меня осудят, но) фактически, монада в Haskell — это стратегия вычисления.

Почему же тогда говорят "поместить/извлечь значение в/из монаду/ы"? Говорят так для простоты. На самом деле, можно лишь запустить определенное вычисление с определенным входным значением (отсюда и идиома run...). Слишком похоже на "запустить процедуру", не так ли?

Так что с термином "монада" либо создатели Haskell были недостаточно дальновидны, либо, просто, хотели эпатировать программистский мир.

2. Подход к изучению
К сожалению, большинство объяснений монад так или иначе отталкиваются от того, что монада — это всего навсего тип и 2 простые функции. Но проблема в том, что от того, что вы поймете, как работают функции return и bind, вам не станет сразу ясно, зачем они нужны вместе. Отталкиваться в понимании того, что такое монады стратегии вычисления, нужно от того, зачем они нужны? Сейчас я сделал именно так, и все легко стало на свои места.

Какие бывают стратегии вычисления? Я сейчас не буду называть самую важную из них, а напишу о ней в следующей записи — пока предлагаю прийти к ответу самостоятельно. Могут быть: вычисление, которое завершается неудачно, если неудачно завершается хотя бы одно из внутренних подвычислений — это знаменитая монада Maybe, которую мы сейчас рассмотрим. Может быть вычисление, которое использует какое-то доступное для всех подвычислений "хранилище" данных — State. Есть вычисление, которое связанно с обменом данными с внешней средой — IO и т.д.

Бытует мнение, что Haskell — ленивый язык (более того, это официальная позиция). На самом деле, все немного сложнее или проще, зависит от того, как посмотреть. Во-первых, всем понятно, что абсолютно (или, как говорится, чисто) ленивого языка быть не может, поскольку в таком случае мы никогда не получим какого-то результата работы программы. Каким образом Haskell'исты выходят из этого затруднения? Принято говорить, что, в целом, язык ленивый, а вся неленивость/энергичность (eagerness) находится (так и хочется сказать, заточЕна) внутри монад. Но дело в том, что любая Haskell-программа находится внутри монады IO. В итоге оказывается, что ленивыми в Haskell являются только чистые функции, а все вычисления (в понимании программистском), которых, на самом деле, большая часть — все равно остаются энергичными. В то же время любая чистая Haskell-функция, учитывая pattern matching и немутируемые переменные, по большому счету представляют из себя одну case-конструкцию и вызов других функций. Красиво? Часто, да. Однобоко? Тоже. ОК, но самое интересное дальше.

И в других языках примерно то же самое! По сути, разница только в том, что:
(а) не проведена четкая граница между ленивыми вычислениями и энергичными. Впрочем, каждый может для себя ее проводить сам: например, можно думать о ленивых вычислениях как об аттрибутах "математических" функций (и таким образом их и программировать), а об энергичных — как о процедурах. В любом языке может быть реализована ленивая стратегия вычислений, однако не везде это сделать одинаково легко...
(б) программист самостоятельно может строить процесс вычисления адекватный конкретной задаче. По сути, построение вычислительных процессов и есть одно из главных составляющих собственно того, что мы понимаем под программированием
Можно выдвинуть предположение, что все разные виды вычислений можно свести к нескольким десяткам определенному количеству монад. И, изучив их, можно будет составлять вычисления, как кубики в конструкторе. (И это предположение действительно выдвигается). В таком случае монады и вправду становились бы решающим методом абстракции в программировании. Впрочем, оказывается, что даже сведя вычисления к монадам, составлять их также легко, как кубики, не удается, и приходится прибегнуть к помощи еще одних монад — монадных преобразователей (monad transformers). В итоге, монада на монаде сидит и монадой погоняет... :) (Можно было догадаться исходя из принципа вычислительной эквивалентности. Вообще говоря, приняв во внимание этот принцип, можно понять, что максима "there is no silver bullet" таки справедлива всегда).
И еще одно: вывод про сведение всех вычислений к определенному набору не нов. Он лежит в основе структурного программирования, где доказано, что все вычисления можно свести к комбинации последовательного выполнения, условного выражения и цикла.

Вот тут то разница между Haskell'ем и другими языками становится наиболее существенной: другие языки имеют синтаксическую поддержку определенного узкого набора монад, и дают возможность создавать другие монады ad hoc с использованием того или иного механизма. Haskell, по сути, делает то же самое, только создание новых монад регуляризирует и вгоняет в определенные рамки. Итак Haskell — это язык, в котором дисциплина на первом месте.

В общем, концепция монад очень полезна, чтобы понять, что могут быть совершенно любые стратегии вычислений и не стоит считать, что возможны только те, которые определенный язык считает родными (вот, в С не было стратегии вычисления Exception, а в С++ появилась :)

Возвращаясь к Maybe. Его все понимают, так ведь? Это нужно для того, чтобы если где-то в последовательности вычислений у нас получился элемент Null (Nothing), это значение в итоге вернулось в качестве результата, и при этом другие части вычисления не имели проблем с обработкой такого значения.

Этот шаблон хорошо знаком всем, кому доводилось встречаться с логическими операторами с коротким замыканием. Идеально он работает в Common Lisp, где значение nil также является логической ложью (это, кстати, считается многими одним из кардинальных преимуществ Common Lisp над Scheme, в котором эти 2 значения разделены). Ниже приводится пример реализации Maybe на CL, аналогичный примеру из пособия по монадам:

(defmacro maybecall (val &rest funs)
"Consequently apply a sequence of FUNctionS to the
return value of the previous computation, starting with VAL"
`(and-it ,val
,@(mapcar (lambda (fun)
`(funcall ,fun it))
funs)))

;; где:
(defmacro and-it (&rest args)
"Like AND, but IT is bound to the value of the previous computation"
(cond ((null args) t)
((null (tail args)) (first args))
(t `(let ((it ,(first args)))
(when it
(and-it ,@(tail args)))))))

;; работает?

(defun mother (x)
(gethash x *mothers*))

(defun father (x)
(gethash x *fathers*))

(setf (gethash :b *fathers*) :e
(gethash :a *fathers*) :d
(gethash :b *mothers*) :c
(gethash :a *mothers*) :b)

(maybecall :a #'mother #'father) => :e
(maybecall :a #'mother #'father #'father #'brother) => nil
;; и даже на brother не ругается, потому что до него дело не доходит


Впрочем, я понял, что обычный AND (или AND-IT) в CL — это и есть полноценная реализация Maybe стратегии. Притом простая и понятная, нет?

3. Нужны ли монады за пределами Haskell'я?
То, что в других языках представляет из себя единую ткань вычислений, в рамках Haskell разделено на 2 класса: чистые (обычные функции) и "грязные" (монады). Это приводит к разделению (дублированию) синтаксиса и усложнению программного кода (полная противоположность дуалистического подхода). В то же время, это делает рассуждения о программах более структурированными и помогает (однако не гарантирует!) соблюдению принципа ссылочной целостности. В теории концепция монад полезна для понимания того, что такое вычисления. На практике же введение дополнительных правил приводит к тому, что появляется определенный предел выразительности языка и близости его к предметной области. Именно так, несмотря на заявления программистов на Haskell об обратном: концепция монады, монадного трансформера и т.д. неимоверно далеки от любой предметной области, которая встречается при программировании, а абстрагировать их средствами Haskell не удастся. Более того, само понятие вычисления, которое в других языках остается имплицитным, в Haskell'е достается на поверхность. Именно с этим, по-моему, связанны частые претензии к преувеличенной сложности Haskell программ.

---

В следующей записи я дам ответ на вопрос про самую главную монаду, а также попробую разобрать по-отдельности несколько других монад.

Еще по теме:
* All About Monads
* Why monads have not taken the Common Lisp world by storm

33 comments:

kmmbvnr said...

За что не любдю блоггер, так это за то что, тут никто не камментит посты.

love5an said...

>За что не любдю блоггер, так это за то что, тут никто не камментит посты.
+1 :)

archimag said...

@Vsevolod
Очень понравилось, понимания предмета у меня стало явно больше.

Вообще, стоило назвать пост "О монадах без религиозного трепета" :)

Artyom Shalkhakov said...

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

Говорят, в Москве кур доят. Да и на заборах пишут всякое...

> Да, но математика и инженерия (в частности, программирование) — разные дисциплины.

Абсолютно верно, но как Вы вывели, что математические термины не могут быть полезными в программировании? (Стоит заметить, что вся "инженерия" держится на физике и математике.)

> А термин монада не относится к предметной области программирования.

Относится с тех пор, как Philip Wadler (или кто там был?) начал пропагандировать применение монад как решение некоторых проблем ФП.

> (Пусть меня осудят, но) фактически, монада в Haskell — это стратегия вычисления

Скорее стратегия комбинирования вычислений (если Вы об операторе bind).

> Почему же тогда говорят "поместить/извлечь значение в/из монаду/ы"?

Если дано вот такое:

data Option a = Some a | None
return x = Some x
bind x f = case x of
None -> None
Some a -> f a

То вполне можно ожидать применение метафоры "положить", "взять".

> Так что с термином "монада" либо создатели Haskell были недостаточно дальновидны, либо, просто, хотели эпатировать программистский мир.

Лол, это все заговор!!!1

> Но проблема в том, что от того, что вы поймете, как работают функции return и bind, вам не станет сразу ясно, зачем они нужны вместе.

Как же. Вы гарантируете?

> Отталкиваться в понимании того, что такое монады стратегии вычисления, нужно от того, зачем они нужны?

Да, правильно. От мотивации надо отталкиваться.

> Принято говорить, что, в целом, язык ленивый, а вся неленивость/энергичность (eagerness) находится (так и хочется сказать, заточЕна) внутри монад.

Нет. Понятие "монада" ортогонально стратегии вычислений (call-by-name/need и call-by-value).

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

Нет, это не так. let a = [putStrLn "foo", putStrLn "bar", putStrLn "baz"] in putStrLn "hi" выводит только "hi".

Дальше начинается вообще какая-то путаница.

> не проведена четкая граница между ленивыми вычислениями и энергичными.

Какая граница? Зачем? В OCaml, ATS ленивые вычисления имеют тип 'a lazy (или как-то так, где lazy -- thunk).

> Впрочем, каждый может для себя ее проводить сам: например, можно думать о ленивых вычислениях как об аттрибутах "математических" функций (и таким образом их и программировать), а об энергичных — как о процедурах.

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

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

Да, можно. Обычно выражение, которое нужно вычислить "потом", просто заворачивают в функцию () -> a, а потом при ее вызове делают все что нужно.

Уфф, устал. В общем, дальше в статье тоже довольно плачевно.

Artyom Shalkhakov said...

Продолжим.

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

Что такое "единая ткань вычислений"? Мейнстримные языки обычно поддерживают два вида "вычислений": математические выражения и инструкции (statements), причем вторые обычно нужны для того, чтобы задать порядок побочных эффектов.

> в рамках Haskell разделено на 2 класса: чистые (обычные функции) и "грязные" (монады).

Нет же. В каком смысле bind для Maybe, например, можно отнести к "классу" "грязных" вычислений?

> Это приводит к разделению (дублированию) синтаксиса и усложнению программного кода (полная противоположность дуалистического подхода).

Где, интересно, дублируется синтаксис? И как он может "дублироваться"? :3

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

Haskell соблюдает ссылочную целостность. Например, let a = putStrLn "foo" in a и putStrLn "foo" абсолютно одинаковые.

> В теории концепция монад полезна для понимания того, что такое вычисления.

Wow, а мы-то думали...

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

Где примеры?

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

Как-то беспредметно. :) Давайте примеры того, как Haskell не позволяет что-то абстрагировать, потом выводите из этого, что Haskell никогда не позволит ничего абстрагировать, и тогда обсудим.

> Более того, само понятие вычисления, которое в других языках остается имплицитным, в Haskell'е достается на поверхность. Именно с этим, по-моему, связанны частые претензии к преувеличенной сложности Haskell программ.

Нет, в Haskell тоже довольно много "имплицитного", поверьте на слово. И нет, программы на Haskell зачастую проще, чем на других языках. Например, этот факт имеет место быть в случае с Xmonad и [подставьте другой WM с подобной функциональностью].

Vsevolod said...

@Artyom
Спасибо за подробный разбор, некоторые вещи мне стали лучше понятны.

По некоторым пунктам:
> [терминология]
Понимаете, от того, что кто-то начал что-то пропагандировать, это не стало понятным большиству программистов. Т.е. если кто-то "аутист" -- то это его проблема, условно говоря, а не общества

> Скорее стратегия комбинирования вычислений (если Вы об операторе bind).
Я о монадах в целом. Ведь в операторе return вычисление тоже производятся, нет?

> Как же. Вы гарантируете?
Говорю только о своем опыте. Думал, это понятно из контекста

> Нет. Понятие "монада" ортогонально стратегии вычислений (call-by-name/need и call-by-value).
call-by -- это calling convention (соглашение о вызове). А стратегрия вычисления -- как по мне, понятие намного шире

> let a = [putStrLn "foo", putStrLn "bar", putStrLn "baz"] in putStrLn "hi" выводит только "hi".
in -- это монадная операция или нет?

> Нет. При строгой стратегии вычислений аргументы...
Мы с вами говорим о разных понятиях: я не о calling convention, а о разных подходах к организации вычислительных процессов. Кажется, вы так это и не уловили. Во многих других языках есть возможность не вычислять значения при вызове (например, в CL это правило действует для macros'ов)

> Мейнстримные языки обычно поддерживают два вида "вычислений"
Все намного сложнее, да и языков много, в каждом свои особенности

> Нет же. В каком смысле bind для Maybe, например, можно отнести к "классу" "грязных" вычислений?
Но есть же монады, которые выполняют побочные эффекты, нет?

> Где, интересно, дублируется синтаксис?
Самый простой пример: = и <-. Разве они делают не то же самое?

> Haskell соблюдает ссылочную целостность.
Мне показалось, что ссылочная целостность будет гарантированно соблюдена, если монады, которые вы объявляете, будут удовлетворять математическому определению (набору правил) монад. Однако, возможности автоверификации этого у компилятора нет

> Где примеры?
Ну, например, то, с чего все началось http://lionet.livejournal.com/44305.html. Можно ли, не меняя подход (т.е. решая через монады State и Maybe), абстрагировать монадный трансформер? Или вы считаете, что монадный трансформер со своим особым синтаксисом и т.д. имеет какое-то отношение к этой предметной области?

> что Haskell никогда не позволит ничего абстрагировать
вы читает между строк, но, к сожалению, то, чего я и близко не говорил. Я сказал, что не удастся в Haskell'е абстрагировать употребление монад, а оно, в целом, довольно сложное и далеко от предметной области. Т.е. если вы в Lisp'е можете написать программу практически полностью на языке предметной области и использовать только те компоненты самого языка, которые сами посчитаете подходящими, то в Haskell определенный базис (включающий монады) всегда будет оставаться в любой программе. Я специально посмотрел первую главу книги про DSL в Haskell (Астапова) и убедился в этом. Сравните, например, с таким простейшим примером: http://lispm.dyndns.org/mov/dsl-in-lisp.mov (125 MB)

> Нет, в Haskell тоже довольно много "имплицитного", поверьте на слово.
Верю :)

> И нет, программы на Haskell зачастую проще, чем на других языках.
Верю :)

> Например, этот факт имеет место быть в случае с Xmonad и [подставьте другой WM с подобной функциональностью].
Придется снова поверить вам на слово, поскольку пока никто не написал в деталях, как это классно внутри устроено. Можно было бы попробовать сравнить со StumpWM. This is left as an exercise to the reader ;)

archimag said...

> Мейнстримные языки обычно поддерживают
> два вида "вычислений"

Мне кажется, что Haskell-программистам не хватает кругозора :) Придя, обычно, в haskell именно из Мейстрима (включая C) они часто кажется не подозревают, что есть ещё много других языков.

> Мы с вами говорим о разных
> понятиях: я не о calling
> convention, а о разных подходах к
> организации вычислительных процессов.

Кажется, Artyom Shalkhakov просто не читал SICP :)

> Можно было бы попробовать
> сравнить со StumpWM.

А вот это не стоит. Я пробовал разбираться с иходным кодом StumpWM и был очень разочарован, ибо неверны определённы абстрации, в частности, недостаточно чётко определённо понятие фрэйма, что очень важно для тайлового wm. Впрочем, XMmonad тоже ещё то поделие :) я несколько раз пытался на него перейти (как и на StumpWM), но каждый раз возращался на Ion3 - в этой области он безусловной лучший.

Artyom Shalkhakov said...

> Понимаете, от того, что кто-то начал что-то пропагандировать, это не стало понятным большиству программистов.

Нет. Во-первых, отношение термина к предметной области не зависит от *количества* людей, которые его знают (можно говорить только о распространенности, не более того). Во-вторых, понимание -- штука весьма сложная и неоднозначная. Могу сказать, что например MVC и ООП понимает очень мало народу, хотя все вроде бы пользуются, но это тема для другого разговора.

> call-by -- это calling convention (соглашение о вызове). А стратегрия вычисления...

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

> in -- это монадная операция или нет?

Нет, это синтаксический сахар. let val = exp in body означает нечто вроде "дать выражению exp имя val в выражении body". Например, в scheme есть let (отличается от let в haskell!) и letrec (наиболее близкий аналог).

> Мы с вами говорим о разных понятиях: я не о calling convention, а о разных подходах к организации вычислительных процессов.

Тогда я не понимаю, что, по-Вашему, такое "энергичные" и "ленивые" вычисления?

> Но есть же монады, которые выполняют побочные эффекты, нет?

"Выполняют"? Скорее "производят", ну да без разницы.

Да, разумеется. Но что считать побочным эффектом? Можно сказать, что Maybe моделирует побочный эффект, заключающийся в прерывании дальнейшего вычисления. Maybe просто абстрагирует паттерн.

> Все намного сложнее, да и языков много, в каждом свои особенности

Да ну, правда? :) C/C++, Java, JS, Python, Fortran и еще целая куча языков построена именно на таком принципе.

> Самый простой пример: = и <-.

Разумеется, нет. do-запись это синтаксический сахар: do {a <- e; return (f a)} это тоже самое, что и e >>= \a -> return (f a).

> Мне показалось, что ссылочная целостность будет гарантированно соблюдена

Во-первых, ссылочную целостность лучше называть "подставляемостью", а определяется она так: f(x) = f(x), где f -- произвольная функция, а x -- такой аргумент, для которого f определена. Если в языке все переменные неизменяемы, и нет способа вызвать что-нибудь такое, что даст наблюдаемый эффект, то вряд ли есть способ нарушить ссылочную целостность. (что-то мне в голову не приходят контрпримеры, но я их поищу, интересно :))

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

> Можно ли, не меняя подход (т.е. решая через монады State и Maybe), абстрагировать монадный трансформер?

А что там абстрагировать? Там нет даже лишних повторений. Но если сильно хочется, то можно объявить новую монаду, внутри которой и спрятать этот трансформер.

> Или вы считаете, что монадный трансформер со своим особым синтаксисом и т.д. имеет какое-то отношение к этой предметной области?

У монадных трансформеров нет своего особого синтаксиса. :) Монадный трансформер может и не относится к предметной области (о какой мы говорим? я запутался уже), но его там почти и нету.

> Я сказал, что не удастся в Haskell'е абстрагировать употребление монад, а оно, в целом, довольно сложное и далеко от предметной области.

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

> Т.е. если вы в Lisp'е можете написать программу практически полностью на языке предметной области

Я рад за лисперов.

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

Слишком сильное обобщение.

> Я специально посмотрел первую главу книги про DSL в Haskell (Астапова) и убедился в этом. Сравните, например, с таким простейшим примером: http://lispm.dyndns.org/mov/dsl-in-lisp.mov (125 MB)

Что-то у меня нет желания скачивать 125MB ради примера.

Artyom Shalkhakov said...

> Придя, обычно, в haskell именно из Мейстрима (включая C) они часто кажется не подозревают, что есть ещё много других языков.

Что же, спуститесь до моего уровня, и покажите эти Ваши "много других языков".

> Кажется, Artyom Shalkhakov просто не читал SICP :)

Поясните, как моя скромная персона относится к предмету обсуждения?

archimag said...

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

Ну, поскольку данный блог во многом посвящён Common Lisp, то наверное с него и надо начинать?

> Поясните, как моя скромная персона
> относится к предмету обсуждения?

Стратегия вычислений одна из ключевых концепций, излагаемая в SICP. И ссылка в посте на "стратегию вычислений" мгновенно развеяла туман у меня в голове по поводу монад. Вы же, кажется, не совсем поняли этот термин, и отсюда идёт неверное понимание поста и неверная аргументация.

Artyom Shalkhakov said...

2 archimag

> Ну, поскольку данный блог во многом посвящён Common Lisp, то наверное с него и надо начинать?

Дискуссия-то идет о Haskell и монадах, не так ли? Причем здесь Лисп и все-все-все? Очень похоже на попытку задать strawman argument (ведь на остальные, гораздо более важные аргументы Вы ничего не ответили).

> Стратегия вычислений одна из ключевых концепций, излагаемая в SICP.

Неужели это такая сложная штуковина, что ее нельзя определить одним предложением и подкрепить парой примеров?

(и да, под стратегией вычислений я понимаю evaluation strategy aka reduction strategy в лямбда-исчислении)

> Вы же, кажется, не совсем поняли этот термин, и отсюда идёт неверное понимание поста и неверная аргументация.

Тогда Вам следует указать, как и где именно я заблуждаюсь. Сказавши "а", говорите и "б".

archimag said...

> Сказавши "а", говорите и "б".

Не вижу в этом смысла, в последнее время и так было достаточно "достойных" примеров обсуждений и кажется не все ещё остыли :) Я же предпочитаю писать код, который считаю единственным критерием. Я бы ещё понял обсуждения в стиле: вот мой код (только не в 20 строк), делающий что-то интересное, а вот код на других языках, и так много раз. Я просто поговорить теоретически это как бы не очень интересно.

Artyom Shalkhakov said...

2 archimag

> Я же предпочитаю писать код, который считаю единственным критерием.

Да, это понятно. Тогда Ваши слова о "неверной аргументации", пожалуй, ничего не значат.

> Я бы ещё понял обсуждения в стиле: вот мой код (только не в 20 строк), делающий что-то интересное, а вот код на других языках, и так много раз.

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

Vsevolod said...

@Artyom
> Нет. Во-первых, отношение термина к предметной области не зависит от *количества* людей, которые его знают
Как по мне, в первую очередь оно зависит от принятия его авторитетами в предметной области (или у вас другой критерий?)
Авторитетов в программировании много (можно ориентироваться, например, на http://en.wikipedia.org/wiki/List_of_programmers. Если выделить из них тех, кто активно пишет/выступает, то сколько из этих употребляет термин "монада"? Мне кажется, -> 0).

> in -- это синтаксический сахар. let val = exp in body означает нечто вроде "дать выражению exp имя val в выражении body".
Правильно, соответственно вычисление производится по ленивому принципу (поскольку монады в toplevel не фигурируют).

> Тогда я не понимаю, что, по-Вашему, такое "энергичные" и "ленивые" вычисления?
То же самое, что и у других.
Когда я говорил про стратегии вычисления, то имел в виду: вычисления, которые могут прерваться в любой момент, вычисления с мемоизацией и т.д. и т.п. То, будут они ленивыми или энергичными -- это уже технический (можно сказать, более низкоуровневый) аспект реализации. К сожалению, ни термин монада, ни стратегия вычисления не являются абсолютно адекватными. Трудно придумать хорошую терминологию, согласен.

> Да ну, правда? :) C/C++, Java, JS, Python, Fortran и еще целая куча языков построена именно на таком принципе.
Вам нужно было сказать не мейнстримные, а языки из Algol-семейства (среди мейнстримных было, есть и будет еще много других). На счет того же Python: оператор yield вы к какому классу относите?

> Разумеется, нет. do-запись это синтаксический сахар: do {a <- e; return (f a)} это тоже самое, что и e >>= \a -> return (f a).
Т.е. в "мейнстримном" языке было бы: a = e, return f(a), так ведь? И для случая, когда e -- "обычное" значение, и когда оно -- в монаде (блин, терминологии нормальной нет или я ей, просто, не владею). Нет?

> Во-первых, ссылочную целостность лучше называть "подставляемостью", а определяется она так: f(x) = f(x), где f -- произвольная функция, а x -- такой аргумент, для которого f определена. Если в языке все переменные неизменяемы, и нет способа вызвать что-нибудь такое, что даст наблюдаемый эффект, то вряд ли есть способ нарушить ссылочную целостность. (что-то мне в голову не приходят контрпримеры, но я их поищу, интересно :))
Так в том то и дело, что нет смысла в языке, где нет такого способа. Поэтому и в Haskell, например, можно сделать так: внутри f используется "монада" (конструкция языка, не математический объект), которая дает непредсказуемый эффект, соответственно ссылочная целостность не соблюдается. Это возможно?

> Во-вторых, программисты выясняют, подчиняется ли монада законом или нет сами. К тому же, построить монаду, которая не будет подчиняться законам можно, просто это не будет монадой. :)
Она не будет монадой в математическом смысле, но в коде вы ее сможете использовать наравне с другими "Haskell монадами", и это может привести к непредсказуемым результатам. ("Any type constructor with return and bind operators that satisfy the three monad laws is a monad. In Haskell, the compiler does not check that the laws hold for every instance of the Monad class. It is up to the programmer to ensure that any Monad instance he creates satisfies the monad laws.")
Это я к тому, что все равно, как и другие паттерны, этот нужно понимать и правильно реализовывать.

> А что там абстрагировать? Там нет даже лишних повторений. Но если сильно хочется, то можно объявить новую монаду, внутри которой и спрятать этот трансформер.
Ну, можно попробовать. Было бы интересно глянуть.
А абстрагировать нужно не только повторения, но, самое главное -- сложность.

> Слишком сильное обобщение.
Возможно. (Покажите контр-пример).

Vsevolod said...

@Artyom
> Да, вообще-то самым конструктивным было бы определить два языка, в одном из которых есть изменяемые переменные, а в другом -- нету, и сравнить стиль программирования на каких-то "реальных" задачах.
А какое отношение к монадам имеет немутируемость переменных? И еще: это сравнение происходит каждый день :)

Artyom Shalkhakov said...

> Как по мне, в первую очередь оно зависит от принятия его авторитетами в предметной области (или у вас другой критерий?)

Вроде бы у Вас был другой критерий (про количество).

> Если выделить из них тех, кто активно пишет/выступает, то сколько из этих употребляет термин "монада"? Мне кажется, -> 0

То есть Вы теперь считаете, что если термин "монада" употребляет, скажем, только пара программистов из того списка, то он не относится к программированию? А если (безграмотный) термин "object-based" говорят многие, то он относится к программированию?

> соответственно вычисление производится по ленивому принципу (поскольку монады в toplevel не фигурируют).

Монада IO фигурирует в top-level (main :: IO a), если я правильно понял, что под top-level Вы понимаете main. Причем тут "ленивый принцип"? (и что это?)

> Вам нужно было сказать не мейнстримные, а языки из Algol-семейства (среди мейнстримных было, есть и будет еще много других).

Э... ну какие же?

> На счет того же Python: оператор yield вы к какому классу относите?

Наличие в Python оператора yield никоим образом не влияет на наличие в том же Python разделения на выражения и инструкции.

> Т.е. в "мейнстримном" языке было бы: a = e, return f(a), так ведь? И для случая, когда e -- "обычное" значение, и когда оно -- в монаде (блин, терминологии нормальной нет или я ей, просто, не владею). Нет?

Нет, return в Haskell и ключевое слово return не имеют ничего общего. Но операцию bind в IO-монаде можно считать за этакую "точку с запятой".

> Это возможно?

Это возможно только для монады IO и только с использованием unsafePerformIO. Может быть, что и при использовании FFI что-нибудь вылезет (не могу сказать, FFI почти не пользовался).

> Она не будет монадой в математическом смысле, но в коде вы ее сможете использовать наравне с другими "Haskell монадами

В коде ее можно будет использовать, но не наравне с монадами.

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

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

> Возможно. (Покажите контр-пример).

main = putStrLn "foo" >> putStrLn "bar"

Такая программа, хоть и бесполезна, не требует глубокого знания монад.

> А какое отношение к монадам имеет немутируемость переменных? И еще: это сравнение происходит каждый день :)

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

archimag said...

> Монады это не паттерны. (!)
> Паттерны проектирования

Ну я же говорю, что не хватает кругозора, ну при чём тут "Паттерны проектирования"? Паттерны и "Паттерны проектирования" это ведь не одно и тоже? Блин, Вы же спорите с человек, который пишет на Common Lisp, а не на C#, учитывайте это :)))

Artyom Shalkhakov said...

Ах, дорогой archimag, перестаньте уже переходить на личности, и позвольте же вести техническую дискуссию. Если Вам есть что дополнить или подкорректировать, то Вы всегда это можете сделать.

archimag said...

> перестаньте уже переходить на
> личности, и позвольте же вести
> техническую дискуссию.

Ок, удаляюсь, пойду лучше писать код ;)

Vsevolod said...

> То есть Вы теперь считаете, что если термин "монада" употребляет, скажем, только пара программистов из того списка, то он не относится к программированию?
в принципе, да. А как по вашему определить принадлежность терминологии определенной сфере? Как по мне, это общеупотребимость и одинаковая трактовка всеми. Следует она из того, что какая-то группа людей начинает активно использовать, а затем подтягиваются все остальные. Понятно, что акждый термин вводится в определенный момент, но он может как прижится, так и нет. Это определеяется тем, насколько он связан с дргой терминологией, насколько адекватен, насколько точно описывает предмет. В случае с монадами это, к сожалению, не совсем наблюдается.

> А если (безграмотный) термин "object-based" говорят многие, то он относится к программированию?
А как вы определелили его безграмотность?

> соответственно вычисление производится по ленивому принципу (поскольку монады в toplevel не фигурируют).
toplevel для того кода, который вы написали это let что-то = что-то in что-то

> Э... ну какие же?
PL/1, Cobol, Lisp, Smalltalk, Mathematica, Forth, Shell (bash), ... -- да очень много языков в разных сферах и разные времена были весьма распространенными.

> Наличие в Python оператора yield никоим образом не влияет на наличие в том же Python разделения на выражения и инструкции.
Тем не менее, вы не ответили на мой вопрос

> Нет, return в Haskell и ключевое слово return не имеют ничего общего.
я понял. я про =

> Это возможно только для монады IO и только с использованием unsafePerformIO. Может быть, что и при использовании FFI что-нибудь вылезет (не могу сказать, FFI почти не пользовался).
Вот вам пример: http://www.engr.mun.ca/~theo/Misc/haskell_and_monads.htm
Там есть вызов getCols. Он соблюдает ссылочную целостность?

> В коде ее можно будет использовать, но не наравне с монадами.
здесь (http://www.haskell.org/all_about_monads/html/laws.html) написано иначе

> Монады это не паттерны. (!) Паттерны проектирования (а) не подчиняются никаким законам, (б) не абстрагируются языком программирования (поэтому их каждый раз нужно реализовывать заново).
http://norvig.com/design-patterns/img007.gif

> Изменение переменной -- побочный эффект, потому что функция, которая изменяет переменную, выполняет что-то еще (а именно это изменение) кроме вычисления своего результата. Но с помощью изменяемых переменных можно получить много полезного, например, состояние. Как получить то же самое, но без изменения переменных?
Мне почему-то кажется, что монады были введены для того, чтобы справится с ленивостью (т.е. неопределенным порядком вычислений -- в некоторых случаях его таки нужно четко определять, да), а не с немутируемыми состоянием. Как по вашему справляются с этим OCaml, Erlang, Clojure, в которых монад в явной форме нет? (ну, то есть, это, по-моему, риторический вопрос)

Artyom Shalkhakov said...

> Как по мне, это общеупотребимость и одинаковая трактовка всеми. Следует она из того, что какая-то группа людей начинает активно использовать, а затем подтягиваются все остальные.

Хорошо, значит, Вы утверждаете, что программисты на Haskell

> А как вы определелили его безграмотность?

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

> toplevel для того кода, который вы написали это let что-то = что-то in что-то

(let val = exp in body) является обычным выражением.

> PL/1, Cobol, Lisp, Smalltalk, Mathematica, Forth, Shell (bash), ... -- да очень много языков в разных сферах и разные времена были весьма распространенными.

Ну давайте на примерах. Я пытаюсь показать, что программы на Си, Cobol и Shell состоят из:
- математических выражений (которые не могут состоять из инструкций)
- инструкций (которые состоят из математических выражений)
- функций/процедур (которые группируют инструкции)

В Си: [x + 1] это выражение, [int a = x + 1;] это инструкция (statement), а [int inc(int x) {int a = x + 1; return a * 2;}] это функция.

Причем надо заметить, что выражения не могут состоять из инструкций (в Си это правило нарушается, в результате -- undefined behavior). Пример: [x++ + x++] -- что дает вычисление этого выражения? Выполняется ли равенство [(x++ + x++) + x++)] = [x++ + (x++ + x++)]?

А теперь укажите на неверность моего утверждения:

> Мейнстримные языки обычно поддерживают два вида "вычислений": математические выражения и инструкции (statements), причем вторые обычно нужны для того, чтобы задать порядок побочных эффектов.

Достаточно будет показать один мейнстримный (=широко используемый в индустрии, например, Java, C#, etc.) язык, который не использует инструкции для указания порядка побочных эффектов. Вы ведь это утверждение хотели опровергнуть, так?

> Тем не менее, вы не ответили на мой вопрос

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

> я понял. я про =

Ах, Вы о синтаксисе... В общем-то, никакой разницы.

> http://www.engr.mun.ca/~theo/Misc/haskell_and_monads.htm
Там есть вызов getCols. Он соблюдает ссылочную целостность?

> здесь (http://www.haskell.org/all_about_monads/html/laws.html) написано иначе

Теперь представьте, на что будет похожа арифметика, если операция сложения не будет ассоциативна, а нуль не будет ее нейтральным элементом.

> http://norvig.com/design-patterns/img007.gif

Да, там на слайде как раз подтверждение. Спасибо.

> Мне почему-то кажется, что монады были введены для того, чтобы справится с ленивостью (т.е. неопределенным порядком вычислений -- в некоторых случаях его таки нужно четко определять, да), а не с немутируемыми состоянием.

Нет изменяемого состояния -- нет необходимости в указании порядка его изменения.

> Как по вашему справляются с этим OCaml, Erlang, Clojure, в которых монад в явной форме нет? (ну, то есть, это, по-моему, риторический вопрос)

Попробуйте вычислить выражение [(\x -> 1) (myglobal := 4)] строго, а затем нестрого (не вычисляя выражение [myglobal := 4] (да, это будет выражение, как set! в Scheme), потому что для завершения вычисления оно не нужно).

Artyom Shalkhakov said...

Упс, не дописал про программистов на Haskell.

> в принципе, да. А как по вашему определить принадлежность терминологии определенной сфере?

Использование этого термина в какой-либо научной публикации или по историческим причинам.

> Как по мне, это общеупотребимость и одинаковая трактовка всеми. Следует она из того, что какая-то группа людей начинает активно использовать, а затем подтягиваются все остальные.

Это говорит только распространенности термина.

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

Эпитеты "адекватен", "связан с другой терминологией", "точно описывает предмет" оставим на потом. Монады имеют (а) точное математическое определение, (б) связь с теорией категорий (которая, в свою очередь -- связь с другими разделами математики), (в) большую абстрактность (наверное, именно это и является основной проблемой для их понимания).

Vsevolod said...

@Artyom
мне кажется, мы как-то с вами говорим немного на разных языках

> А теперь укажите на неверность моего утверждения
А вы сначала ответьте на мой вопрос про Python ;)
а еще на такой: в Haskell'е есть "инструкции" (statemets)?

> Ах, Вы о синтаксисе
Разве я сразу не сказал? ("Это приводит к разделению (дублированию) синтаксиса")

Про GetCols опять же ответа нет

> Теперь представьте, на что будет похожа арифметика, если операция сложения не будет ассоциативна, а нуль не будет ее нейтральным элементом.
Мы говорим о программировании, а не об арифметике. Разница в том, что рассматривается вопрос, может ли ипрограммирование на Haskell позволить достигнуть лучшего качества кода по человеко-центричным метрикам (поддерживаемость, модульность, понятность и т.д.). Ведь с теоретической точки зрения программа на C и программа на Haskell Тьюринг-эквивалентны, т.е. разницы между ними нет.

> Нет изменяемого состояния -- нет необходимости в указании порядка его изменения.
Хорошо, у нас есть программа:
print 1
while (True): do nothing

Глобального состояния нет, но если не задан порядок вычислений, то print 1 может выполнится, а может нет, так ведь? Зачем нам такая программа? ;)
Что касается монад для управления состоянием. Согласитесь, что это только один из способов, альтернатива которому, явно передавать состояние через вызовы функций (по сути, монады это и делают, только скрывая. пример: do-нотация)

> Использование этого термина в какой-либо научной публикации или по историческим причинам.
мы снова говорим на разных языках: программирование -- это не научная дисциплина, не путайте с Computer Science. Это гуманитарная дисциплина: тут нужно, чтобы humanity восприняло ;)

Artyom Shalkhakov said...

> мне кажется, мы как-то с вами говорим немного на разных языках

Да, вероятно.

> А вы сначала ответьте на мой вопрос про Python ;)

Зачем? Вы же просто уводите разговор в сторону.

> а еще на такой: в Haskell'е есть "инструкции" (statemets)?

Есть только ближайший эквивалент: вычисление в монаде IO.

> Разве я сразу не сказал? ("Это приводит к разделению (дублированию) синтаксиса")

Ну просто это слишком уж поверхностное рассуждение, извините.

> Про GetCols опять же ответа нет

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

> Мы говорим о программировании, а не об арифметике.

В программировании разве не применяется арифметика? (Пример с плюсом и 0 показан был лишь для того, чтобы сравнить его с >>= и return: return является для >>= единичным элементом -- прям как 0 для +, >>= ассоциативен -- как +)

> Разница в том, что рассматривается вопрос, может ли ипрограммирование на Haskell позволить достигнуть лучшего качества кода по человеко-центричным метрикам (поддерживаемость, модульность, понятность и т.д.).

Вроде бы рассматривался вопрос "о страшных монадах", а тут вдруг качество кода...

Что же касается этого вопроса, то я могу только сказать, что сообщество Haskell генерирует очень много интересных идей. Некоторые из них могут оказаться полезными.

> Ведь с теоретической точки зрения программа на C и программа на Haskell Тьюринг-эквивалентны, т.е. разницы между ними нет.

Мы же не с теоретической точки зрения рассматриваем?

> Хорошо, у нас есть программа:
print 1
while (True): do nothing

Что это? Как строятся выражения этого языка? Как он интерпретируется?

> Глобального состояния нет, но если не задан порядок вычислений, то print 1 может выполнится, а может нет, так ведь? Зачем нам такая программа? ;)

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

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

Ну я нигде не утверждал обратное, совсем.

> (по сути, монады это и делают, только скрывая. пример: do-нотация)

Да, только вот монады можно использовать для много другого: например, для продолжений (в вебе особенно полезно), для реактивного программирования, etc. И нет, do-нотация ничего такого не скрывает.

> мы снова говорим на разных языках: программирование -- это не научная дисциплина, не путайте с Computer Science. Это гуманитарная дисциплина: тут нужно, чтобы humanity восприняло ;)

Программирование это software engineering. Всякий engineering строится на точных науках. В случае с SE это будет CS и математика. Может быть, Вы знаете о каких-то альтернативных основах для программирования?

Vsevolod said...

@Artyom
> Зачем? Вы же просто уводите разговор в сторону.
У меня, к сожалению, сложилось такое же впечатление и о некоторых ваших ответах

> Есть только ближайший эквивалент: вычисление в монаде IO.
"Today we will start learning about the case statement." (http://learnhaskell.blogspot.com/2007/09/lesson-3-case-3.html)

> Ну просто это слишком уж поверхностное рассуждение, извините.
ничего страшного :)

> В программировании разве не применяется арифметика?
есть разница между применяется и является основой. Вполне возможно, что операция + в каком-то языке будет не ассоциативной (например, в PHP можно "сложить" число и строку. Вполне может быть, что это неассоциативно: например, просиходит неявное приведение типов к типу первого аргумента. Лень проверять, если честно).

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

> Мы же не с теоретической точки зрения рассматриваем?
Вот именно!

> Что это? Как строятся выражения этого языка? Как он интерпретируется?
аналог Haskell без монад ;) (т.е.
main = do
print 1
while (True): do nothing
мы записать не сможем)

> Ну я нигде не утверждал обратное, совсем.
Вы сказали, что монады "нужны" для управления состоянием. А я сказал, что они "нужны" для управления порядком вычислений. Нет?

> Программирование это software engineering. Всякий engineering строится на точных науках. В случае с SE это будет CS и математика. Может быть, Вы знаете о каких-то альтернативных основах для программирования?
С научной точки зрения, как мы уже согласились, программа на С и на Haskell эквивалентны. Т.е. нет предмета для нашей дискуссии. Возможно, есть еще какие-то составляющие программирования, которые приводят к тому, что мы все-таки обсуждаем, стоит ли последовательность вычислений задавать через монады или через statements и т.п.? Как вы считаете? ;)

Artyom Shalkhakov said...

> "Today we will start learning about the case statement." (http://learnhaskell.blogspot.com/2007/09/lesson-3-case-3.html)

case .. of (сопоставление с образцом) не имеет никакого отношения к заданию порядка побочных эффектов.

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

Я говорил об операции сложения для натуральных чисел. Конечно, при overflow ассоциативность не выполняется.

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

>> Ну я нигде не утверждал обратное, совсем.

> Вы сказали, что монады "нужны" для управления состоянием. А я сказал, что они "нужны" для управления порядком вычислений. Нет?

Гм. Вы уже совсем запутались, и запутали меня. :) ("Управление состоянием" -- слишком размытый термин, "управление порядком вычислений" тоже.)

> Возможно, есть еще какие-то составляющие программирования, которые приводят к тому, что мы все-таки обсуждаем, стоит ли последовательность вычислений задавать через монады или через statements и т.п.? Как вы считаете? ;)

Я считаю, что иногда лучше одно, а в иных случаях другое.

AnnemarieSchimmel said...

很好啊 ..................................................

ADEpt said...

Хотелось бы заметить, что название monad - это только один из представителей длиииного ряда названий, взятых "из теории": Functor, Applicative, Monoid, Traversable, Foldable, Monad.

И именно поэтому не нужно заниматься заменой терминов. Это...мммм... ну как если бы в java предложить заменить слово "method" на "operation" - ведь в большнинстве случаев в теле метода указывается последовательность операций :)

Хорошая библиография по теме: http://squadette.livejournal.com/599819.html

Vsevolod said...

@ADEpt Проблема, на которую я пытался указать, заключается в том, что берутся понятия из одной предметной области и пересаживаются в другую. Это приводит к тому, что для большинства программистов, которые не имеют соответствующей базы, нет предпосылок для понимая этих концепций (и возникает напряжение, которое хорошо выражено в приснопамятном посте zabivator'а). Почему эту алгебраическую/категорную терминологию не пытаются "приблизить" как-то к остальной мне не понятно. То ли считаются (слышал такие заявления), что это не нужно — ну, тогда вечно и будут продолжаться эти споры, – то ли не понимается этого момента, то ли это создает какой-то ореол научности. Возможно, все вместе. (Я не отрицаю, что в основе Haskell реальная, настоящая наука. Но также и в основе инженерии физика и математика, но терминология все равно своя, и совсем иные принципы).
Итак, возвращаясь к методам в Java: название, кстати, идет из CLOS и означает конкретный (специализированный) способ выполнения той или иной операции (функции). Как по мне, оно из "общечеловеческой" что ли области, поэтому не является концептуальным барьером. Можно еще взять другие примеры (даже из ФП): замыкание — в данном случае имеет смысл, весьма отличный от математического,— свёртка — то же не соответствует тому, что в математике. Но благодаря такой терминологии, сами концепции становятся легче для понимания. Разве нельзя монаду также объяснить простыми словами? (я попытался: может быть и не удачно) ТО же касается и моноидов, стрелок, функторов: ведь в данном случае мы оперируем конкретными экземлярами этих концепций...

thesz said...

http://en.wikipedia.org/wiki/Evaluation_strategy

Находится по "computation strategy".

Что там об "использование нетипичной для предметной области терминологии — это аттрибут троллинга"? ;)

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

Vsevolod said...

@thesz, не совсем понял, к чему это относится: к тому, что выражение "стратегия вычисления" уже занято и означает несколько иное. Возможно. Значит, можно подобрать более точный термин из предметной области. Впрочем, в той же википедии написано (http://en.wikipedia.org/wiki/Monads_in_functional_programming): "monad is a kind of abstract data type used to represent computations".

rigidus said...

мне кажется что в макросе and-it есть ошибка в запятой:
нужно
"(t `(let ((it ,(first args)))
а не
(t `(let ((it (first ,args)))
я прав?

Vsevolod Dyomkin said...

@rigidus Да, спасибо за поправку