История изменений
Исправление quasimoto, (текущая версия) :
Но выглядит тормозным (это оно машинный код на стеке пишет во время выполнения) и неудобным (максимум один nest).
Поэтому на уровне фронтенда и рантаймовых фишек (чтобы не говорить «VM»). Например:
f(x, y) = x + y
g(x) = \y -> f x y
будет
struct f_int_int_int_capture_first { int x; int(&f)(int, int); };
int call(f_int_int_int_capture_first &fn, int y) { return fn.f(fn.x, y); }
int f(int x, int y) { return x + y; }
f_int_int_int_capture_first& g(int x) { return *new /* gc_new */ f_int_int_int_capture_first{x, f}; }
int main() { return call(g(1), 2); }
В фронтенде всё знаем — про типы и захваты, так что аллоцируем такие структуры и сами расставляем call, в IR для этого всё есть. Типичная проблема с необходимостью счётчика ссылок или GC для замыканий — тоже имеет место быть. Ну и разные агрессивные оптимизации к ним применимы, как в C++ или в Haskell.
Исходная версия quasimoto, :
Но выглядит тормозным (это оно машинный код на стеке пишет во время выполнения) и неудобным (максимум один nest).
Поэтому на уровне фронтенда и рантаймовых фишек (чтобы не говорить «VM»). Например:
f(x, y) = x + y
g(x) = \y -> f x y
будет
struct f_int_int_int_capture_first { int x; int(&f)(int, int); };
int call(f_int_int_int_capture_first &fn, int y) { return fn.f(fn.x, y); }
int f(int x, int y) { return x + y; }
f_int_int_int_capture_first& g(int x) { return *new /* gc_new */ f_int_int_int_capture_first{x, f}; }
int main() { return call(g(1), 2); }
➜ ~ g++ --std=c++11 -O2 closure.cc; ./a.out; echo $?
3
➜ ~ g++ --std=c++11 -O2 -S closure.cc; cat closure.s
.file "q.cc"
.text
.p2align 4,,15
.globl _Z1fii
.type _Z1fii, @function
_Z1fii:
.LFB1:
.cfi_startproc
leal (%rdi,%rsi), %eax
ret
.cfi_endproc
.LFE1:
.size _Z1fii, .-_Z1fii
.p2align 4,,15
.globl _Z4callR27f_int_int_int_capture_firsti
.type _Z4callR27f_int_int_int_capture_firsti, @function
_Z4callR27f_int_int_int_capture_firsti:
.LFB0:
.cfi_startproc
movq 8(%rdi), %rax
movl (%rdi), %edi
jmp *%rax
.cfi_endproc
.LFE0:
.size _Z4callR27f_int_int_int_capture_firsti, .-_Z4callR27f_int_int_int_capture_firsti
.p2align 4,,15
.globl _Z1gi
.type _Z1gi, @function
_Z1gi:
.LFB2:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl %edi, %ebx
movl $16, %edi
call _Znwm
movl %ebx, (%rax)
movq $_Z1fii, 8(%rax)
popq %rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE2:
.size _Z1gi, .-_Z1gi
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB3:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $16, %edi
call _Znwm
movl $1, (%rax)
movq $_Z1fii, 8(%rax)
movl $3, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
В фронтенде всё знаем — про типы и захваты, так что аллоцируем такие структуры и сами расставляем call, в IR для этого всё есть. Типичная проблема с необходимостью счётчика ссылок или GC для замыканий — тоже имеет место быть. Ну и разные агрессивные оптимизации к ним применимы, как в C++ или в Haskell.