Это уточнение/доработка Как выглядит стек вызовов в системе вложенных функций, функций, передаваемых в качестве аргументов, и возвращаемых функций?
Вот некоторый вымышленный код, который демонстрирует 2 модуля и некоторые вложенные вызовы функций.
// module_1.js
// global variables
var CONST = 6541
function add(a, b) {
return CONST + a + b
}
function sub(a, b) {
return CONST - a - b
}
function mul(a, b) {
return CONST * a * b
}
// module_2.js
// global variables
var a = 11
var b = 5
var c = 2
main()
function main() {
var v = foo_1()
var w = foo_2()
var t = v(1, 2, 3)
var u = w(4)
var k = add(t, u)
// OUTPUT
console.log(k)
// nested functions
function foo_1() {
var d = bar_1(2, 3)
var e = bar_2(10)
// returning functions as results.
return bar_3
// super nested functions.
function bar_1(x, y) {
var m = mul(x, a)
var n = sub(m, y)
var o = mul(n, c)
return o
}
function bar_2(x) {
var m = mul(x, x)
var n = add(m, 3)
var o = mul(n, a)
return n
}
function bar_3(x, y, z) {
var m = mul(x, a)
var n = sub(m, y)
var o = add(n, b)
var p = mul(o, z)
var q = sub(p, c)
var r = mul(q, d)
var s = add(r, e)
return s
}
}
function foo_2() {
return bar_2
function bar_1(x, y) {
var p = add(y, a)
var q = add(x, p)
return q
}
function bar_2(x) {
var p = bar_1(x, 2)
var q = bar_1(x, 4)
var r = mul(p, a)
var s = add(q, c)
return r * s
}
}
}
Итак, в основном есть 2 модуля, каждый из которых использует глобальные переменные в своих функциях. Второй модуль имеет ряд вложенных функций и возвращает функции в некоторых точках.
То, что я пытаюсь сделать, это собрать воедино порядок операций, который будет выполнять компилятор. Это то, что у меня есть до сих пор после многих часов:
main.closure = createClosure(a, b, c)
main.foo_1.closure = createClosure(parent: main.closure, d, e)
main.foo_1.bar_1.closure = createClosure(parent: main.foo_1.closure, x, y, m, n, o)
mul.closure = createClosure(CONST, a, b)
То есть каждая функция инициализируется (заворачивается) в замыкание.
Затем вы начинаете вызывать функции:
-> call(main)
-> main.env = createEnvironment({
closure: main.closure,
a: 11,
b: 5,
c: 2
})
-> call(foo_1)
-> main.foo_1.env = createEnvironment({
closure: main.foo_1.closure,
// with parent environment
parent: main.env,
d: null,
e: null
})
-> call(bar_1, 2, 3)
-> main.foo_1.bar_1.env = createEnvironment({
closure: main.foo_1.bar_1.closure,
// with parent environment
parent: main.foo_1.env,
x: 2,
y: 3,
m: null,
n: null,
o: null
})
-> call(mul, main.foo_1.bar_1.env.x, main.foo_1.bar_1.env.parent.parent.a)
-> mul.env = createEnvironment({
closure: mul.closure,
// no parent environment
CONST: 6541,
a: main.foo_1.bar_1.env.x,
b: main.foo_1.bar_1.env.y
})
Или сплющенный, как это было бы на самом деле:
-> call(main) =
-> main.env = createEnvironment({
closure: main.closure,
a: 11,
b: 5,
c: 2
})
-> call(foo_1) =
-> main.foo_1.env = createEnvironment({
closure: main.foo_1.closure,
// with parent environment
parent: main.env,
d: null,
e: null
})
-> call(bar_1, 2, 3) =
-> main.foo_1.bar_1.env = createEnvironment({
closure: main.foo_1.bar_1.closure,
// with parent environment
parent: main.foo_1.env,
x: 2,
y: 3,
m: null,
n: null,
o: null
})
-> call(mul, main.foo_1.bar_1.env.x, main.foo_1.bar_1.env.parent.parent.a) =
-> mul.env = createEnvironment({
closure: mul.closure,
// no parent environment
CONST: 6541,
a: main.foo_1.bar_1.env.x,
b: main.foo_1.bar_1.env.y
})
Итак, в основном:
- замыкание создается для каждой функции (перед вызовом любой функции). Он создает в основном список всех переменных, к которым вы можете получить доступ в функции.
- Вызов каждой функции создает новую среду, которая создается на основе замыкания. Это извлекает все переменные, определенные в замыкании, для использования в функции.
- Среда устанавливает все свои переменные в текущие значения. Или, может быть, у него есть указатели на значения или параметры родительской среды?
Я все еще не вижу полной картины. Если бы кто-нибудь мог написать псевдокод для порядка операций, это было бы все, что мне нужно. По сути, как ряд вложенных функций (и функций, определенных в других модулях с другими глобальными переменными) на самом деле реализуется как последовательность примитивных вызовов (для создания сред или чего-то еще).
В основном я пытаюсь обернуть голову вокруг реализации компилятора, который поддерживает эти функции. Мне нужно взять определения моих функций (как AST) и скомпилировать их в примитивные блоки инструкций, подобные сборке. Каждый блок инструкций или метка представляет собой последовательность инструкций (согласно определению сборки). Таким образом, это будет что-то вроде этого псевдокода сборки:
data a, 11
data a, 5
data a, 2
main:
call foo_1
call foo_2
...
foo_1:
push 2
push 3
call bar_1
...
foo_1_bar_1:
...
...
...
foo_2:
...
foo_3:
...
НО это будет включать гораздо больше, оно будет включать среды для каждого вызова. Больше похоже на это:
main:
// create main environment
push a
push b
push c
// some other stuff perhaps for constructing the environment.
call foo_1
...
В общем, я застрял на этапе того, как представить преобразование вложенных функций и модулей, ориентированных на синтаксис языка, в последовательность инструкций, подобную ассемблеру. Особенно в том, что касается создания сред и замыканий.
В псевдокоде, не более элегантном, чем мой, какой должна быть последовательность инструкций для создания этих сред в структуре, похожей на Ассемблер?
function f(n) { a = Array(); for (var i = 0; i<n; ++i) a.push(()=>i); return a; }
и очень похожимfunction g(n) { a = Array(); for (let i = 0; i<n; ++i) a.push(()=>i); return a; }
; разница только вvar
иlet
. Но закрытия очень разные. Сравнитеf(3)[0]()
иg(3)[0]()
. Итак, какой из двух Python? Попробуйтеf=lambda n:[lambda:i for i in range(n)]
. - person rici   schedule 09.09.2020