Рассмотрим программу:
let f = [x].letrec f ac = [n].(. . .)
in + x (f ac 100)
in
+ (f 3)(f 4)
Алгоритм из подраздела 16.6 скомпилирует ее в:
$f ac f ac n = (. . .)
$f x = letrec f ac = $f ac f ac
in + x (f ac 100)
+ ($f 3)($f 4)
Функция f ac определена локально в теле функции f и, следователь-
но, выражение (f ac 100) не может быть поднято как МСВ из тела
f. Это означает, что (f ac 100) будет вычисляться заново всякий раз,
когда выполняется $f. Таким образом, происходит потеря свойства
полной ленивости.
Выход из создавшегося положения достаточно прост: достаточ-
но заметить, что определение f ac не зависит от x, поэтому можно
вынести letrec для fac:
letrec
f ac = [n].(. . .)
in let
f = [x].+ x (f ac 100)
in
+ (f 3)(f 4)
Теперь ничто не препятствует применению полностью ленивого подъ-
ема, который построит полностью ленивую программу:
$f ac n = (. . .)
$f ac100 = $f ac 100
$f x = + x $f ac100
$P rog = + ($f 3)($f 4)
$P rog
В данном случае произведена некоторая оптимизация: выражение,
не имеющее свободных переменных, не абстрагируется, а получает
имя и становится суперкомбинатором -- $f ac100.
Таким образом, стратегия воплощается в два этапа:
1) вынесение letrec- (и let-) определений как можно “дальше”,
2) выполнение полностью ленивого ламбда-подъема.
Упражнение 16.12 Выполнить полностью ленивый ламбда-подъем
программы:
let
g = [x].letrec el = [n].[s].(IF(= n 1)(head s)
(el(− n 1)(tail s)))
in (cons x(el 3 (A, B, C)))
in
(cons(g R)(g L))
(здесь: R иL− константы).
|