История изменений
Исправление quasimoto, (текущая версия) :
В реальности, как минимум в старых языках типа lisp - хрена лысого. Делают вид, что как бы появляется новая функция, но в реале никакой новой функции нету, только старые используются житрожопым образом.
Сделай let over lambda и будет тебе аллокация нового замыкания в куче с «кодогенерацией» (кавычки, так как это не значит, что обязательно будет дёргаться компилятор, можно аллоцировать новое на основе заранее сформированного машинного кода):
* (lambda (x) (+ x 1))
#<FUNCTION (LAMBDA (X)) {1002D942D9}>
* (defun get-closure (x) (let ((z x)) (lambda (y) (incf z y) z)))
GET-CLOSURE
* (defvar *first-closure* (get-closure 0))
*FIRST-CLOSURE*
* (defvar *second-closure* (get-closure 0))
*SECOND-CLOSURE*
* *first-closure*
#<CLOSURE (LAMBDA (Y)) {1002AE7AF9}>
* *second-closure*
#<CLOSURE (LAMBDA (Y)) {1002B48C29}>
* (funcall *first-closure* 1)
1
* (funcall *first-closure* 1)
2
* (funcall *first-closure* 1)
3
* (funcall *second-closure* 1)
1
* (funcall *second-closure* 1)
2
* (funcall *second-closure* 1)
3
*
видим, что создаются два новых и разных (1002AE7AF9 и 1002B48C29) замыкания в куче (то есть собираемых GC). Для такого вот поведения они и должны быть разные и новые. В хаскеле, например, с иммутабельностью весь чистый код подвергается lambda liftingу и интенсивному inliningу, так что до рантайма не доживают ни лямбды (т.к. нет fixpoint оператора), ни замыкания (которых в чистом коде вообще нет) - только CAFs которые довольно легко статически компилировать. Реальные замыкания как в лиспе (= новые, каждый раз разные, в куче, собираемые GC) там возникнут только если их явно сделать через IO (с лямбдами с мутабельностью и IO lambda liftingу уже не справиться).
Ну я грешным делом подумал, что раз уж так прям про лямбду пишут, то наверно достигли того, что в учебнике написано - код генерят и всё такое. Оказалось - хрен там, одно унылое говно, да и то в качестве костыля.
Радоваться же надо, что код лишний раз в рантайме не генерируется (нет зависимости от компилятора, накладных расходов на тяжёлую генерацию и т.п.).
Исправление quasimoto, :
В реальности, как минимум в старых языках типа lisp - хрена лысого. Делают вид, что как бы появляется новая функция, но в реале никакой новой функции нету, только старые используются житрожопым образом.
Сделай let over lambda и будет тебе аллокация нового замыкания в куче с «кодогенерацией» (кавычки, так как это не значит, что обязательно будет дёргаться компилятор, можно аллоцировать новое на основе заранее сформированного машинного кода):
* (lambda (x) (+ x 1))
#<FUNCTION (LAMBDA (X)) {1002D942D9}>
* (defun get-closure (x) (let ((z x)) (lambda (y) (incf z y) z)))
GET-CLOSURE
* (defvar *first-closure* (get-closure 0))
*FIRST-CLOSURE*
* (defvar *second-closure* (get-closure 0))
*SECOND-CLOSURE*
* *first-closure*
#<CLOSURE (LAMBDA (Y)) {1002AE7AF9}>
* *second-closure*
#<CLOSURE (LAMBDA (Y)) {1002B48C29}>
* (funcall *first-closure* 1)
1
* (funcall *first-closure* 1)
2
* (funcall *first-closure* 1)
3
* (funcall *second-closure* 1)
1
* (funcall *second-closure* 1)
2
* (funcall *second-closure* 1)
3
*
видим, что создаются два новых и разных (1002AE7AF9 и 1002B48C29) замыкания в куче (то есть собираемых GC). Для такого вот поведения они и должны быть разные и новые. В хаскеле, например, с иммутабельностью весь чистый код подвергается lambda liftingу и интенсивному inliningу, так что до рантайма не доживают ни лямбды (т.к. нет fixpoint оператора), ни замыкания (которых в чистом коде вообще нет) - только CAFs которые довольно легко статически компилировать. Реальные замыкания как в лиспе (= новые, каждый раз разные, в куче, собираемые GC) там возникнут только если их явно сделать через IO (с лямбдами с мутабельностью и IO lambda lifting уже не справиться).
Ну я грешным делом подумал, что раз уж так прям про лямбду пишут, то наверно достигли того, что в учебнике написано - код генерят и всё такое. Оказалось - хрен там, одно унылое говно, да и то в качестве костыля.
Радоваться же надо, что код лишний раз в рантайме не генерируется (нет зависимости от компилятора, накладных расходов на тяжёлую генерацию и т.п.).
Исходная версия quasimoto, :
В реальности, как минимум в старых языках типа lisp - хрена лысого. Делают вид, что как бы появляется новая функция, но в реале никакой новой функции нету, только старые используются житрожопым образом.
Сделай let over lambda и будет тебе аллокация нового замыкания в куче с «кодогенерацией» (кавычки, так как это не значит, что обязательно будет дёргаться компилятор, можно аллоцировать новое на основе заранее сформированного машинного кода):
* (lambda (x) (+ x 1))
#<FUNCTION (LAMBDA (X)) {1002D942D9}>
* (defun get-closure (x) (let ((z x)) (lambda (y) (incf z y) z)))
GET-CLOSURE
* (defvar *first-closure* (get-closure 0))
*FIRST-CLOSURE*
* (defvar *second-closure* (get-closure 0))
*SECOND-CLOSURE*
* *first-closure*
#<CLOSURE (LAMBDA (Y)) {1002AE7AF9}>
* *second-closure*
#<CLOSURE (LAMBDA (Y)) {1002B48C29}>
* (funcall *first-closure* 1)
1
* (funcall *first-closure* 1)
2
* (funcall *first-closure* 1)
3
* (funcall *second-closure* 1)
1
* (funcall *second-closure* 1)
2
* (funcall *second-closure* 1)
3
*
видим, что создаются два новых и разных (1002AE7AF9 и 1002B48C29) замыкания в куче (то есть собираемых GC). Для такого вот поведения они и должны быть разные и новые. В хаскеле, например, с иммутабельностью весь чистый код подвергается lambda liftingу и интенсивному inliningу, так что до рантайма не доживают ни лямбды (т.к. нет fixpoint оператора), ни замыкания (которых в чистом коде вообще нет) - только CAFs которые довольно легко статически компилировать. Реальные замыкания как в лиспе (= новые, каждый раз разные, в куче, собираемые GC) там возникнут только если их явно сделать через IO (с лямбдами с мутабельностью lambda lifting уже не справиться).
Ну я грешным делом подумал, что раз уж так прям про лямбду пишут, то наверно достигли того, что в учебнике написано - код генерят и всё такое. Оказалось - хрен там, одно унылое говно, да и то в качестве костыля.
Радоваться же надо, что код лишний раз в рантайме не генерируется (нет зависимости от компилятора, накладных расходов на тяжёлую генерацию и т.п.).