Арность неизвестна
Арность известна
Выражения
::=
Атом
Вызов функции (
Вызов примитивной функции (
let
Выделение нового объекта
case
Приведение выражения
Альтернативы
::=
Сопоставление с образцом (
Альтернатива по умолчанию
Объекты в куче
::=
Функция арности
Частичное применение
указывать только на
Полное применение конструктора (
Отложенное вычисление
Используется только во время
выполнения программы
Программа
::=
Рис. 10.2: Синтаксис STG
По синтаксису STG можно понять, какие выражения языка Haskell являются синтаксическим сахаром. Им
просто нет места в языке STG. Например, не видим мы сопоставления с образцом. Оно как и if
-выраженияпереписывается через case
-выражения. Исчезли where-выражения. Конструкторы могут применяться толь-ко полностью, то есть для применения конструктора мы должны передать ему все аргументы. В STG let
-выражения разделяют на не рекурсивные (let
) и рекурсивные (letrec). Разделение проводится в целях оп-тимизации, мы же будем считать, что эти случаи описываются одной конструкцией.
На что стоит обратить внимание? Заметим, что функции могут принимать только атомарные значения
(либо примитивные значения, либо переменные). В данном случае переменные указывают на объекты в куче.
Так если в Haskell мы пишем:
foldr f (g x y) (h x)
В STG это выражение примет вид:
let
gxy = THUNK (g x y)hx
= THUNK
(h x)in
foldr f gxy hx
У функций появились степени. Что это? Степени указывают на арность функции, то есть на количество
принимаемых аргументов. Количество принимаемых аргументов определяется по левой части функции. По-
скольку в Haskell функции могут возвращать другие функции, очень часто мы не можем знать арность, тогда
мы пишем
Отметим два важных принципа вычисления на STG:
• Новые объекты создаются в куче
• Выражение приводится к СЗНФ
Язык STG | 157
Выражение let
a = obj in e означает добавь в кучу объект obj под именем a и затем вычисли e.Выражение case
e of~{alt1; ... ;alt2} означает узнай конструктор в корне e и продолжи вычисления всоответствующей альтернативе. Обратите внимание на то, что сопоставления с образцом в альтернативах
имеет только один уровень вложенности. Также аргумент case
-выражения в отличие от функции не обязанбыть атомарным.
Для тренировки перепишем на STG пример из раздела про ленивые вычисления.
data Nat = Zero | Succ Nat
zero
= Zero
one
= Succ
zerotwo
= Succ
onefoldNat ::
a -> (a -> a) -> Nat -> afoldNat z
s
Zero
=
zfoldNat z
s
(Succ
n)=
s (foldNat z s n)add a =
foldNat aSucc
mul a =
foldNat one (add a)exp =
(\x -> add (add x x) x) (add Zero two)Теперь в STG:
data Nat = Zero | Succ Nat
zero
= CON
(Zero)one
= CON
(Succ zero)two
= CON
(Succ one)foldNat = FUN
( z s arg ->case
arg ofZero
->
zSucc
n-> let
next = THUNK (foldNat z s n)in
s next
)
add
= FUN
( a ->let
succ = FUN( x ->let
r = CON(Succ x)in
r)in
foldNat a succ
)
mul
= FUN
( a ->let
succ = THUNK (add a)in
foldNat one succ
)
exp
= THUNK
(let
f = FUN( x -> let axx = THUNK (add x x)in
add axx x)
a = THUNK
(add Zero two)in
f a
)
Программа состоит из связок вида имя = объектКучи
. Эти связки называют глобальными, они становятсястатическими объектами кучи, остальные объекты выделяются динамически в let
-выражениях. Глобальныйобъект типа THUNK
называют постоянной аппликативной формой (constant applicative form или сокращённоCAF).
10.3 Вычисление STG
Итак у нас есть упрощённый функциональный язык. Как мы будем вычислять выражения? Присутствие
частичного применения усложняет этот процесс. Для многих функций мы не знаем заранее их арность. Так
например в выражении
158 | Глава 10: Реализация Haskell в GHC
f x y
Функция f может иметь один аргумент в определении, но вернуть функцию. Есть два способа вычисления
таких функций:
•
в стек, затем совершаем
ло аргументов, которое ей нужно, после этого мы извлекаем из стека необходимое число аргументов, и
применяем к ним функцию, если мы снова получаем функцию, тогда мы опять добираем необходимое
число аргументов из стека. И так пока аргументы в стеке не кончатся.
•