72 7.1. Термы и контексты
Каждый раз при распечатке переменной мы будем проверять, что это число совпадает с реальной
длиной контекста, где мы находимся; если это не так, значит, где-то мы забыли добавить операцию
сдвига.
Последнее улучшение тоже относится к распечатке. Несмотря на то, что внутри термы представля-
ются через индексы де Брауна, пользователю, они, очевидно, в таком виде показываться не должны:
мы должны преобразовывать из обыкновенного представления в безымянное во время чтения, а во вре-
мя распечатки преобразовывать термы обратно в обыкновенную форму. В этом нет ничего сложного,
однако проделывать это совсем наивным образом не стоит (скажем, порождая для всех переменных
совершенно новые имена), поскольку тогда имена связанных переменных в печатаемых термах будет
никак не связан с именами из исходной программы. Можно справиться с этой трудностью, если хранить
при каждой абстракции строку-подсказку об имени связанной переменной.
type term =
TmVar of info * int * int
| TmAbs of info * st ring * term
| TmApp of info * term * term
Основная работа с термами (в частности, подстановка) ничего интересного с этими строками проде-
лывать не будет: они просто переносятся в результат в исходной форме, безо всякой заботы о совпа-
дении имен, захвате переменных, и т. д. Когда программе распечатки требуется породить новое имя
для связанной переменной, она сначала пытается использовать подсказку; если окажется, что это имя
противоречит какому-либо имени, уже используемому в текущем контексте, процедура распечатки пы-
тается породить похожее имя, добавляя штрихи, пока, в конце концов, не найдется имя, которое в
текущем контексте никто не использовал. Такое правило обеспечивает, что напечатанный терм будет
всегда очень похож на то, чего ожидает пользователь, с точностью до нескольких штрихов.
Сама процедура печати выглядит так:
let rec printt m ctx t = match t with
TmAbs ( fi ,x , t1 ) ->
let ( ctx ’,x ’) = p i c k f r e s h n a m e ctx x in
pr " ( lambda ␣"; pr x ’; pr ". ␣ "; printt m ctx ’ t1 ; pr ")"
| TmApp ( fi , t1 , t2 ) ->
pr " ( "; printtm ctx t1 ; pr " ␣"; pr inttm ctx t2 ; pr ")"
| TmVar ( fi , x ,n) ->
if ctxlength ctx = n then
pr ( i n d e x2name fi ctx x)
else
pr " [ bad ␣ index ]"
В ней используется тип данных context,
type contex t = ( string * binding ) list
который представляет собой просто список строк и соответствующих им связываний. Пока что связы-
вания совершенно тривиальны
type bindin g = NameB ind
и не несут никакой дополнительной информации. Однако позднее (в Главе 10) мы введем другие вари-
анты в определение типа binding, и они будут отслеживать сведения о типе, связанном с переменной,
и другую подобную информацию.
Процедура распечатки зависит также от нескольких низкоуровневых функций: pr выдает строку в
стандартный поток вывода; ctxlength возвращает длину контекста; index2name находит строковое имя
переменной по ее индексу. Самая интересная из этих функций, pickfreshname, принимает контекст ctx
и имя-подсказку x, находит имя x’, похожее на x и не встречающееся в ctx, добавляет x’ к контексту
ctx, получая при этом новый контекст ctx’, и возвращает пару, состоящую из x’ и ctx’.
Настоящая процедура печати в интерпретаторе untyped с веб-сайта книги выглядит несколько слож-
нее, поскольку принимает во внимание еще несколько деталей. Во-первых, она по возможности избегает
печатать скобки, следуя соглашению, что применение право-ассоциативно, а тела абстракций простира-
ются насколько возможно вправо. Во-вторых, она порождает форматные команды для низкоуровневого
rev. 104