ВВефал

6. МЕТАСИСТЕМНЫЙ ПЕРЕХОД

     6.1. Метафункция  Mu 
     6.2. Метакод
     6.3. Вычислитель
     6.4. Замораживатель

При написании программы мы имеем дело с некоторым объектным полем -- множеством объектов, к которым мы обращаемся, когда используем функции. Это множество включает области определения и области значения всех функций, т.е. множества их допустимых аргументов и значений. Будем считать, что это множество объектов основного уровня, или уровня 0, в иерархии объектов. Уровнем 1 в этой иерархии является РЕФАЛ-машина с загруженными в нее функциональными определениями. Взаимодействием между этими двумя уровнями является процесс вычислений, при котором объекты уровня 0 составляют материал, "плоть'' процесса, в то время как РЕФАЛ-машина управляет созданием и преобразованием этого материала. Будем называть систему S1 уровня 1, которая  осуществляет управление совокупностью систем S0 уровня 0, метасистемой по отношению к уровню 0.

Эта двухуровневая иерархия расширяется, когда создается следующий уровень управления, который имеет дело с РЕФАЛ-машиной и с функциональными определениями как с управляемыми объектами. Назовем такой шаг метасистемным переходом.

 

6.1. МЕТАФУНКЦИЯ  Мu

Читатель мог бы уже заметить, что в варианте нашего языке, определенном до настоящего момента, нет способа применения таких функций, для которых имя не задается явно, а является значением некоторой переменной либо результатом вычислений. Согласно синтаксису РЕФАЛа, после открывающей вычислительной скобки обязательно должно следовать символическое имя функции (идентификатор). Это требование, которое, разумеется, введено с целью эффективной реализации, не позволяет использовать такие выражения как:

  <s.F e.X> ,

которое можно было бы записать для того, чтобы вызвать функцию, чье имя определяется динамически как значение  переменной s.F . Однако РЕФАЛ-5 позволяет достичь того же эффекта посредством использования выражения:

  <Mu s.F e.X> 

Здесь Mu является встроенной функцией, или, скорее,  метафункцией, осуществляющей вызов функции с таким именем, которое задается символом, непосредственно следующим за  Mu . Остальная часть выражения становится аргументом этой функции. Таким образом, Mu работает, как будто определена следующим предложением:

  Mu { s.F e.X = <s.F e.X> } ,

если оно синтаксически допустимо.

Применяя Mu, можно определять различного рода интерпретаторы , которые используют выражения в качестве программ, специфицирующих, какие из функций низшего уровня (функции-исполнители) должны быть вызваны. Вот простой пример интерпретатора, который последовательно применяет заданный список операторов к заданному аргументу :

  Use-seq 
    (s.F e.1) On e.X = <Use-seq (e.1) On <Mu s.F e.X>>;
    () On e.X = e.X; 

Модульная структура программ в РЕФАЛе-5 привносит некоторое усложнение в простую идею функции Mu . Предположим, функция Mu уже применена для того, чтобы определить некоторую функцию Callmu в одном модуле, а затем Callmu используется как внешняя функция в другом модуле. Может случиться так, что некоторое имя функции используется в обоих модулях для определения различных локальных функций. Какую их этих функций следует тогда вызывать посредством Mu? Рассуждая подробнее, рассмотрим следующие два модуля -- Mod1 :

  * This module is the main module Mod1.
  * It uses the function Callmu defined in Mod2. 
  * It also defines function F
  * which has a different definition in Mod2.
  $ENTRY Go { =  <Prout 'Mu: ' <Mu F 'ABC'>> 
         <Prout 'Callmu: ' <Callmu F 'ABC'>> }
  $e.XTRN Callmu;
  F { e.1 = 'Mod1' }

и Mod2 :

  * This auxiliary module is Mod2. It defines 
  * a simple interpreting function Callmu,
  * which uses Mu. It also defines function F
  * which is defined differently in Mod1.
  $ENTRY Callmu { s.F e.X = <Mu s.F e.X>; }
  F { e.1 = 'Mod2' }

Можно определить две версии метафункции Mu : Mu-Статическую ( MuS ) и Mu-Динамическую ( MuD ). Согласно статическому определению функциональное значение символического имени определяется модулем, где записана функция, использующая MuS ; в нашем случае этим модулем является Mod2 . Общее правило таково: когда бы ни появился вызов MuS, он может активизировать только функцию, видимую из этой точки (т.е., либо локально определенную, либо входящую в список $e.XTERN), и вызываемая функция будет функцией, определенной в этой точке. При динамическом определении функциональное значение символического имени определяется во время выполнения, т.е. в главном модуле: в нашем случае это Mod1. Пусть выполняется:

  refgo Mod1+Mod2

Если  Mu определена статически (как MuS ), программа выдаст на принтер:

  Mu: Mod1
  Callmu: Mod2

Если же  Mu определена динамически (MuD ), будет напечатано:

  Mu: Mod1
  Callmu: Mod1

Следует привести некоторые соображения как за, так и против указанных способов определения Mu. Динамическое определение выглядит более естественным и соответствует общему принципу: если функция не видима из главного модуля, она не может быть активизирована. С другой стороны, с системной точки зрения хорошо иметь возможность вызвать любую функцию программы, если это нужно. MuD не допускает этого, в то время как  MuS   позволяет это проделать. В самом деле, пусть используются модули с именами Mod1 , Mod2 , Mod3 , и т.д. В Mod1 определяется:

  $ENTRY Mu-Mod1 { e.1 = <Mu e.1> } 

в Mod2 :

  $ENTRY Mu-Mod2 { e.1 = <Mu e.1> } 

и т.д. Тогда, в какой бы момент мы ни пожелали вызвать функции , определенные в некотором модуле Modi , мы вызываем Mu-Modi (не забывайте включать эту функцию в список $e.XTERN). Динамическая функция MuD может быть имитирована применением статической функции Mu . Следует только определить:

  $ENTRY MuD { e.1 = <Mu e.1> } 

в главном модуле и затем использовать MuD в любом модуле (снова объявив ее как внешнюю за пределами главного модуля).

С учетом этого, встроенная функция Mu в нашей РЕФАЛ-системе определяется статически.

Упражнение 6.1 Предположим, определены пятьдесят функций с именами Fun1 , Fun2 ... и т.д. до Fun50 . Определить функцию Fun-n так, чтобы <Fun-n s.N e.X> вызывала s.N -ю функцию из этой последовательности с аргументом e.X . Пример:

  <Fun-n 12 'ABC'>       превращается в     <Fun12 'ABC'>

На начало

6.2. МЕТАКОД

Преобразование программ - это одна из важнейших областей применения РЕФАЛа; как программы, подлежащие преобразованиям (уровень 1, объектные программы), так и программы-преобразователи (уровень 2) обычно будут записываться на РЕФАЛе -- это делает возможной самоприменимость программ-преобразователей.

Для того, чтобы записывать РЕФАЛ-программы, которые оперируют с РЕФАЛ-программами, мы должны представлять объектные программы с помощью некоторых объектных выражений, так как свободные переменные и функциональные скобки, которые используются в объектных программах, не могут подвергаться  преобразованию в РЕФАЛе. В самом деле, предположим, что имеется некоторая программа и нужно преобразовать всякий вызов функции F1 из этой программы в вызов F2 . Предложение, подобное:

  Subst21 { e.1 <F1 e.X> e.2  = 
             e.1 <F2 e.X> <Subst21 e.2>; }

не сработает. Согласно синтаксису РЕФАЛа, активные подвыражения не могут использоваться в левой части. Но даже если бы мы расширили РЕФАЛ, допустив такое использование, активное подвыражение <F2 e.1> в правой части воспринималось бы как процесс, а не как объект; вычисление этого вызова начиналось бы перед следующим далее вычислением вызова Subst21 , даже если бы это являлось нежелательным. Аналогично, мы не можем использовать свободную переменную в качестве объекта преобразования, поскольку она будет воспринята РЕФАЛ-машиной как сигнал к выполнению подстановки.

Отображение всех РЕФАЛ-объектов на множество объектных выражений будет рассматриваться как некоторый  метакод. Такое отображение, разумеется,  должно быть инъективным; т.е. все образы ("метакоды'') различных РЕФАЛ-объектов должны быть различными, так что существует единственное обратное преобразование. Преобразование-метакодирование приводит к понижению метасистемного уровня объекта -- это переход от управляющего устройства к обрабатываемому объекту. Поэтому, когда метакодирование применяется к выражению Е , говорим, что оно   погружается в метакод; когда применется обратное преобразование, говорим, что  Е подымается из метакода. Когда реально создаются РЕФАЛ-программы, работающие с РЕФАЛ-программами, следует выбрать некоторое конкретное метакодирование. Но когда ведутся рассуждения о погружаемых и подымаемых выражениях, часто хочется использовать систему обозначений, позволяющую оставлять такие преобразования неопределенными. Поэтому погружение в метакод будет обозначаться символом "стрелка вниз" ; для подъема из метакода выбирается символ "стрелка вверх" . Когда область действия этих операций распространяется до конца текущего подвыражения, перед ним просто ставится либо . При необходимости ограничить область их действия используются скобки. Например, конкатенация (сцепление) погруженного выражения   Е1   с незакодированным Е2   изображается как {Е1}Е2 , в то время как (Е1)Е2 означает то же, что и ({Е1})Е2. Очевидно,  Е = Е; и если Е существует, то Е = Е.

Целью метакодирования является отображение свободных переменных и вычислительных скобок в объектные выражения. Прекрасно было бы, если б при метакодировании можно было оставить объектные выражения неизменными . К сожалению, это невозможно из-за требования однозначности обратного преобразования. В самом деле, предположим, что имеется такой метакод. Тогда e.1   должно быть равным e.1 , так как e.1 является объектным выражением. Отсюда вытекает, что два различных объекта, e.1 и e.1 , имеют одинаковые метакоды.

Тем не менее, можно минимизировать различия между объектным выражением и его метакодом. Метакод, используемый в РЕФАЛ-системе, выделяет один символ, а именно звездочку '*' , который изменяется при метакодировании. Все другие символы и объектные скобки (круглые) представляют сами себя. Наш метакод определен следующей таблицей:

Выражение Е его метакод Е
s.I '*S'.I
t.I '*T'.I
e.I '*E'.I
< F Е> '*'(( F) Е)
(Е) (Е)
Е1 Е2 {Е1} Е2
'*' '*V'
S (но не '*') S

Таким образом  e.X является '*E'.X , s.1 является '*S'.1 , 'a*b' представляет собой 'a*Vb' , <F e.Arg> является '*'((F)'*E'.Arg) и т.д.

Когда метакодирование применяется в некоторому объектному выражению, его результат может быть вычислен посредством вызова РЕФАЛ-функции Dn , которая заменяет всякий символ '*' на '*V' :

Dn {
   '*'e.1  =  '*V' <Dn e.1>;
   s.2 e.1 = s.2 <Dn e.1>;
   (e.2)e.1 = (<Dn e.2>) <Dn e.1>;
    = ; }

Функция Dn также реализована в РЕФАЛ-системе как встроенная функция, которая производит погружение своего аргумента в метакод за один шаг.

Для произвольного объектного выражения Е0,

  <Dn Е0>  ==  Е0 

Время, необходимое для погружения или подъема объектного выражения пропорционально его длине. Часто является желательным отложить реальное метакодирование некоторого выражения до того момента, когда действительно потребуется применить его погруженную форму, поскольку вполне может случиться, что все выражение, или его часть, будут позднее подыматься к своему оригиналу. Поэтому следует избегать неоправданных двусторонних преобразований выражения, если существует способ пометить выражение, подлежащее метакодированию, но на самом деле не изменяющееся. Мы будем использовать выражение

  '*!'(Е0)  

для представления отложенного метакода выражения Е0. Это не нарушает однозначности обратного преобразования, так как комбинация '*!' не могла бы возникнуть никаким иным путем. Таким образом, обратное преобразование будет просто трансформировать '*!'(Е0) обратно в Е0. Следующее правило помогает управлять отложенным метакодированием: для всякого (не только объектного) выражения,

  '*!'(Е)          является эквивалентным       <Dn Е> 

В самом деле, даже если Е включает свободные переменные и активные подвыражения, подразумеваемая последовательность действий в обеих частях этой эквивалентности одна и та же: первые свободные переменные должны быть замещены объектными выражениями, затем процесс вычисления должен начаться и закончиться, а затем результат должен быть погружен в метакод.

Обратной функцией к  Dn является Up . Если ее аргументом является результат метакодирования объектного выражения Е0, то Up возвращает значение Е0:

  <Up <Dn Е0>>  ==  Е0 

Но область определения Up можно расширить так, чтобы она включала метакоды любого основного (ground-) выражения Еgr (т.е. такого, которое может содержать активные подвыражения, но не содержит свободных переменных). Выберем ground-выражение, например, <F 'abc'> , и погрузим его в объектное выражение '*'((F)'abc') . Теперь, если применить к нему Up , оно будет подыматься назад к исходному активному выражению:

  <Up '*'((F)'abc')>  ==  <F 'abc'> 

которое немедленно запускает процесс вычисления. Вообще, для любого ground-выражения Еgr,

 <Up Еgr>  ==  Еgr 

Функцию Up можно определить в РЕФАЛе следующим образом:

  Up {
     '*V'e.1 = '*'<Up e.1>;
     '*'((s.F) e.1)e.2 = <Mu s.F <Up e.1>> <Up e.2>;
     '*!'(e.2)e.1 = e.2 <Up e.1>;
     s.2 e.1 = s.2 <Up e.1>;
     (e.2)e.1 = (<Up e.2>)<Up e.1>;
      =  ; }

Из определения Up легко видеть, что вычисление вызова <Up Еgr> является имитацией вычисления  Еgr . Эта функция конвертирует "замороженные'' вызовы функций из Еgr в их активную форму и проделывает это в точности в том же порядке изнутри-наружу, как это проделала бы РЕФАЛ-машина.

Упражнение 6.2 Если функция Up встречает первый метакод свободной переменной, например, '*E'.X , она оставит его без изменения, а это было бы ошибкой. Если ее аргумент не является метакодом ground-выражения, эта функция должна быть не определена. В самом деле, подъем выражения '*E'.X  должен приводить к свободной переменной e.X, которая будет помещена в поле зрения; однако, это не допускается определением РЕФАЛ-машины. Модифицировать приведенное выше определение Up таким образом, чтобы в случае, когда ее аргумент не принадлежит области определения,   выдавалось сообщение об ошибке и происходил аварийный останов.

На самом деле, рассмотренное выше определение функции Up представляет чисто академический интерес, так как РЕФАЛ-система   содержит встроенную функцию Up, которая эквивалентна нашему РЕФАЛ-определению для аргумента Еgr, но работает быстрее. Она помещает Еgr в поле зрения РЕФАЛ-машины всего за один шаг. Вдобавок  ее область определения расширена за счет включения метакодов произвольных РЕФАЛ-выражений, а не только ground-выражений. Это будет обсуждено в Разд. 6.4.

Применяя функцию Up , мы имеем те же альтернативы, в терминах модульной структуры, что и для функции Mu . Наша встроенная функция Up является статической. Пользователь должен принимать во внимание, что функции, предназначенные для активизации с помощью Up, являются видимыми из модуля, в котором встречается вызов Up. Если функция, использующая Up, определена во вспомогательном модуле, нужно заменить ее на UpD и определить UpD в главном модуле:

  $ENTRY UpD { e.X = <Up e.X> }

совершенно аналогичным образом, как в трактовке функции Mu .

ЗАМЕЧАНИЕ: Следует иметь в виду различие между и   , с одной стороны, и между Dn и Up , с другой стороны. Первые просто являются метасимволами, используемыми для обозначения   некоторых РЕФАЛ-объектов; сами они не являются ни РЕФАЛ-объектами, ни частью РЕФАЛ-системы. Вторые же являются законными именами функций.

Упражнение 6.3 Развернуть в РЕФАЛ-выражения (если это возможно):

(a) <Add (35)16>
(b) <Up (<F (e.1)e.2>)'*!'(e.3)>
(c) '*'((Comp) 'A*VB')
(d) '*!'('A*B')

Упражнение 6.4 Вычислить:

(a)  <Dn <Add (35)16>>
(b)  <Up '*!'('A*B')>
 

На начало

 

6.3. ВЫЧИСЛИТЕЛЬ

В РЕФАЛ-системе используется специальная функция Go для того, чтобы поместить начальное выражение в поле зрения РЕФАЛ-машины. Когда выражение вычислено, РЕФАЛ-машина останавливается и передает управление операционной системе компьютера. Это простейший способ взаимодействия между пользователем и РЕФАЛ-машиной. Иногда предпочтительнее был бы другой способ. Хотелось бы, чтобы РЕФАЛ-выражение подавалось на вход машины, получался результат его вычисления, и управление возвращалось бы пользователю без выхода из РЕФАЛ-системы, так что следующее выражение могло бы снова подвергнуться вычислению, и так сколько угодно раз.

Если пользователь искушен в написании выражений и захочет, чтобы вычисление производилось в метакоде, тогда решение этой проблемы является весьма простым. Go определяется как вызов интерпретирующей функции Job, которая предлагает пользователю ввести выражение, применяет к нему Up, и когда вычисления закончены, распечатывает результат и запрашивает следующее выражение.

Однако для пользователя может оказаться затруднительным записывать нужные выражения в метакоде; предпочтительнее разрешить ему записывать выражения в том же виде, в каком они появляются в программе. Поэтому, вместо функции Input , которая вводит объектные выражения, применяется другая функция -- назовем ее  Inp-met -- которая считывает выражение и погружает его в метакод. Главное ее отличие от Input состоит в том, что символы '<' и '>' воспринимаются как вычислительные скобки.

Теперь следовало бы подумать о том, как привязать это определение Go к существующим модулям РЕФАЛа. Если бы Up была динамической, не оставалось бы ничего другого как заменить Go в главном модуле на Go, которая вызывает интерпретирующую функцию Job, описанную выше. Это значило бы, что программа может использоваться только в режиме вычислителя.

Однако наша функция Up является статической. Упомянутая выше Go оформляется как отдельный модуль, который называется E (от английского 'Evaluator' - Вычислитель), и модифицируется путем замены Up на UpD и объявления UpD как внешней функции. Для того, чтобы использовать любой модуль, скажем, Prog , совместно с вычислителем, следует добавить к Prog стандартное определение:

  $ENTRY UpD { e.X = <Up e.X> }

Начало программы E выглядит следующим образом:

  * Evaluator E
  $ENTRY Go { =  <Job>; }
  Job { = <Prout 'Type e.Xpression to evaluate. '
                 'To end: empty line.'>
      <Prout 'To end session: empty e.Xpression'>
      <Prout >  <Check-end <Inp-met>>; }
   
  Check-end {
        = <Prout 'End of session'>;
     '*'Error = <Job>;
     e.X = <Out <UpD e.X>>; }
   
  Out {e.X = <Prout 'The result is:'>
            <Prout e.X> <Prout>
            <Job>; }
  $e.XTRN UpD;

Теперь, если Prog является главным модулем (т.е. включает определение Go ), мы можем использовать его в  Go-режиме как прежде:

  refgo prog ...

(где многоточие указано вместо вспомогательных модулей). Но каким бы ни был модуль Prog, главным или вспомогательным, мы  всегда можем использовать его в режиме вычислителя:

  refgo e+prog ...

Теперь модуль-Вычислитель e является, по существу, главным модулем, но имеется доступ к любой функции из Prog через UpD . Стандартное определение UpD может быть добавлено к любому РЕФАЛ-модулю заранее: это не причинит ущерба. На самом деле будет использоваться только UpD из первого модуля, следующего за вычислителем e. Трассировщик reftr может запускаться таким же образом.

Модуль-Вычислитель e.ref поставляется вместе с РЕФАЛ-системой.

ЗАМЕЧАНИЕ: При использовании вычислителя отсутствует интерпретация. Дополнительное время, по сравнению с режимом Go, тратится только на ввод выражения, подлежащего вычислению. Если ввод произведен, Up создает активное выражение в поле зрения, которое выполняется точно так же, как и всегда.

На начало

 

6.4. ЗАМОРАЖИВАТЕЛЬ

При преобразовании программы метакодированные вызовы функций уровня 1 (которые обычно включают некоторые свободные переменные) становятся аргументами функций уровня 2:

  <F2 ... <F1 ... e.1 ...> ... >  

то есть

  <F2 ... '*'((F1) ... '*E'.1 ...) ... >  

Это схематическое преставление ситуации. В аргументе F2 может присутствовать некоторая комбинация вызовов функций наряду с пассивными выражениями; назовем эти комбинации конфигурациями РЕФАЛ-машины, загружаемыми вместе с определениями функций уровня 1.

Основу любой системы преобразования программ в таких языках, как РЕФАЛ, составляет замена   вызова функции значением, которое она порождает, за один шаг алгоритма. Во время преобразования программы в РЕФАЛе вызов сравнивается с левыми частями определяющих функцию предложений, которые, подобно функциональным конфигурациям, используются в метакодированной форме. Затем моделируется шаг РЕФАЛ-машины. Этот процесс известен как прогонка. В процессе прогонки множества допустимых значений свободных переменных разбиваются на подмножества, соответствующие разным предложениям функционального определения. Это делает возможным однозначное выполнение шага для любого подмножества. Предположим, например, что имеется следующее определение функции Fa :

  Fa {
     'a's.X e.1 = 'b's.X <Fa e.1>;
     s.2 e.1 = s.2 <Fa e.1>;
     = ; }

и произведем прогонку вызова <Fa e.1> . Результатом одного шага прогонки является граф, в котором направленные ребра обозначают некоторые подмножества полного множества допустимых значений переменных, в то время как узлы представляют конфигурации, порождаемые при этом шаге. В нашем случае граф будет иметь три ребра (ветви). Первая ветвь определяет шаг для случая, когда значение e.1 начинается с 'a', за которым следует некоторый символ s.X . Это ведет к конфигурации:

  'b's.X <Fa e.1>

где e.1 теперь означает то, что осталось от первоначального выражения e.1 после удаления из него первых двух символов. Вторая ветвь соответствует подмножеству значений, в которых первым символом является не 'a' -- или же это символ 'a', но остальная часть выражения либо пуста, либо начинается с круглой скобки. Третье подмножество состоит из одного элемента: пустого выражения.

Очевидно, что прогонка, как и всякая форма интерпретации, занимает гораздо больше времени, чем непосредственное вычисление. Поэтому, когда в конфигурации не имеется свободных переменных, было бы гораздо быстрее непосредственно вычислять ее, чем использовать общий механизм прогонки. Более того, всякий раз, когда шаг может быть выполнен однозначно, независимо от значений свободных переменных, также было бы очень желательно выполнять его при помощи непосредственного вычисления. Предположим, например, что при рассмотренном выше определении мы имеем конфигурацию:

  <Fa 'a's.1'bc'e.2> 

Независимо от того, каковы значения s.1 и e.2 , в результате получится конфигурация:

  'b's.1 <Fa 'bc'e.2> 

Аналогичным образом, следующие два шага будут однозначными и приводят к:

  'b's.1'bc' <Fa e.2> 

Только теперь следующий шаг зависит от значений переменных , и прогонка является неизбежной. Таким образом, в некоторых случаях прогонка может сводиться к прямому частичному вычислению вызовов функций.

РЕФАЛ-система обеспечивает средства для эффективного частичного вычисления до тех пор, пока непосредственное выполнение шагов РЕФАЛа не зависит от значений свободных переменных . Это достигается следующим образом.

В поле зрения допускается использование объектов дополнительного типа. Эти объекты будут рассматриваться как неизвестные. Информационным наполнением неизвестного является его тип (S , T или E ), его уровень (неотрицальное целое число) и его индекс (макроцифра). Система знает, что неизвестное s-типа означает некоторый символ, а неизвестное t-типа -- некоторый терм; это принимается во внимание при сопоставлении. До тех пор, пока значения неизвестных объектов не требуются для выполнения РЕФАЛ-шага, они переходят из одного выражения в другое и могут быть скопированы. Система не замечает их существования, или вернее, их отличия от нормальных объектных выражений. Неизвестные можно обнаружить с помощью трассировщика, который распечатывает их в виде:

  #type.level  inde.X 

Неизвестные объекты создаются функцией Up и преобразуются с помощью Up и Dn . Если неизвестный объект, имеющий тип t, уровень n, и индекс i, обозначить как:

  unknown(t,n,i)

то расширение этих функциональных определений может быть описано следующими формулами:

  <Up '*'s.T s.I>  =   unknown(s.T,0,s.I);
  <Up  unknown(t,n,i)> =  unknown(t,n+1,i);
  <Dn  unknown(s.T,0,s.I)> = '*'s.T s.I;
  <Dn  unknown(t,n+1,i)> =  unknown(t,n,i);

Имеется встроенная функция Ev-met ('вычислять поверх метакода'). Она воздействует на поле зрения таким образом, словно она определена предложением:

  Ev-met { e.X = <Freezer <Up e.X>>;}

Вдобавок она выполняет некоторые системные операции. Freezer ("Замораживатель") является фиктивной функцией, которая нужна для заключения в скобки выражений, которые возникают при подъеме, и для вычисления аргумента функции Ev-met . Она также "замораживает'' ее аргумент, т.е. погружает его в метакод. Freezer не может быть вызвана пользователем и появляется только как размещаемая функцией Ev-met . После того, как аргумент Ev-met поднят, начинается процесс прямого вычисления. Есть три варианта возможного его завершения:

Заметим, что даже если неизвестные объекты по сути выступают в качестве свободных переменных для обозначения некоторых вещей, следует вводить специальное обозначение для них, чтобы описывать операции над ними с помощью РЕФАЛ-предложений. В самом деле, нечто подобное

  <Up '*E'.X>  =  e.X 

противоречило бы синтаксису РЕФАЛа (свободная переменная в правой части, которая в левой части отсутствует), в то время как:

  <Dn e.X>  =  '*E'.X

определяло бы  Dn как совершенно иную функцию.

Функцию Up следует применять только в случае, когда известно, что ее аргумент не содержит свободных переменных. Если таковые имеются, Up создаст неизвестные объекты, и, поскольку они не заключены замораживателем в скобки, возникнет ошибка, как только неизвестная переменная помешает вычислению. Заметим, что первым действием большинства встроенных функций является преобразование собственных аргументов из списочных структур в массивы. Поэтому они вызывают замораживание даже перед началом своей специальной работы.

С неизвестными нельзя оперировать так же, как и с другими законными ("известными'') объектами РЕФАЛа. Единственное из назначение -- ускорить процесс вычислений. Что бы вы ни намеревались с ними проделать, следует сперва сконвертировать все выражения, содержащие неизвестные объекты, в метакод. Тогда неизвестные становятся метакодированным представлением свободных переменных.

Приведем краткий очерк применения замораживателя при преобразовании программы. Предположим, что необходимо исследовать (метакодированный) вызов функции Fa :

  '*'((Fa) Е) 

где  Е - некоторое выражение-образец. Перед прогонкой используется функция Try-pe ('try partial evaluation' - "попытка частичного вычисления"), которая определена следующим образом:

  Try-pe { e.E = <Checkfr <Ev-met e.E>> }
  * Check the contents of freezer
  Checkfr {
     0 e.E = <Passive e.E>;
     1 e.E = <Drive e.E>;
     2 e.E = <Undefined e.E>;
          }

Когда производится вызов:

  <Try-pe '*'((Fa) Е)> 

вызов Fa подымается из метакода и помещается в замораживатель. Предположим, Е является выражением '*E'.1 (произвольный аргумент). Тогда, не производя ни одного шага, система погрузит его обратно в метакод и перейдет к функции Drive для прогонки. Но если

  Е  =  'a*S'.1 'bc*E'.2 

как в приведенном выше примере, тогда непосредственно выполнятся три шага РЕФАЛ-машины, после чего будет предпринята прогонка. Если  Е является выражением 'a*S'.1'bc' , вычисление будет продолжаться до получения пассивного результата 'b*S'.1'bc' . Тогда функция Passive определит, что делать дальше.

Упражнение 6.5 Модифицировать вычислитель, описанный в Разд. 6.3 таким образом, чтобы в случае, когда встречается аварийный останов, выдавалось сообщение, распечатывалось содержимое поля зрения в метакоде и управление было передано функции Error, которая, по предположению, справится с ситуацией.

На начало