167
различным в зависимости от того, является ли аргумент применения,
переданный функции apply, последним аргументом стандартной
функции. Если это еще не последний аргумент, то его значение просто
присоединяется к списку уже вычисленных значений аргументов, а
количество ожидаемых аргументов для завершения вычислений
уменьшается на единицу. Если же аргумент - последний, то следует
применить
стандартную функцию (выполнить δ-редукцию), для чего
потребуется еще одна вспомогательная функция интерпретатора
intrinsic, осуществляющая работу всех стандартных алгоритмов. Итак,
второе уравнение для функции apply будет выглядеть так:
apply (Oper nArgs func listArgs) arg
| nArgs == 1 = intrinsic func newListArgs
| otherwise = Oper (nArgs-1) func newListArgs
where newListArgs = listArgs ++ [arg]
Теперь мы уже можем исполнять любые программы, не содержащие
блоков. Что касается нерекурсивного блока let, то его реализация
сравнительно несложна. По существу, такой блок ничем не отличается от
вызова безымянной функции с несколькими аргументами, причем тело
блока является телом этой функции, формальные аргументы – это
переменные, определяемые в заголовке блока, а фактическими
аргументами вызова будут выражения, находящиеся в правых частях
связывающих уравнений в заголовке блока. Собственно, можно было бы
перевести нерекурсивный блок на язык лямбда-выражений, однако, проще
и естественнее будет записать уравнения для функции eval в этом случае
непосредственно. Дело еще и в том, что в даном случае нам совершенно не
нужна сложная работа с контекстами, которая была проделана, когда мы
реализовывали в нашем интерпретаторе лямбда-выражения. Выражения в
заголовке блока вычисляются в исходном контексте, а для вычисления
тела блока контекст просто пополняется новыми связями.
Уравнение для случая нерекурсивного блока получается довольно
длинным, но по существу, ничего сложного в нем нет. Прежде
всего, для
того, чтобы получить список новых связей переменных с их
вычисленными значениями к списку пар (переменная, выражение)
применяется поэлементно функция, которая, оставляя первый элемент
пары без изменения, производит вычисление второго элемента пары –
выражения – в заданном контексте с помощью функции eval. Эта
вспомогательная функция задана в уравнении с помощью лямбда-
выражения (\(x, e) -> (x, eval ctx e)). Потом получившийся
список пар соединяется с контекстом ctx, и тело блока вычисляется в
новом, пополненном контексте newCtx.
eval ctx (Let pairs body) = eval newCtx body where
newCtx = (map (\(x, e) -> (x, eval ctx e)) pairs) <++> ctx