Опубликован: 19.03.2004 | Уровень: специалист | Доступ: платный
Лекция 3:

Универсальная функция

< Лекция 2 || Лекция 3: 1234 || Лекция 4 >

Определение универсальной функции

Универсальная функция EVAL, которую предстоит определить, должна удовлетворять следующему условию: если представленная аргументом форма сводится к вычислению функции, имеющей значение на списке аргументов из этой же формы, то данное значение и является результатом функции eval.

(EVAL '(fn arg1 ... argK))
; = результат применения fn к аргументам arg1, ..., argK.

Явное определение универсальной функции позволяет достичь четкости механизмов обработки Лисп-программ.

(EVAL '((LAMBDA (x y) (CONS (CAR x) y)) 
                '(A B) '(C D) ))
; = (A C D)

Вводим две важные функции EVAL-А и APPLY для обработки форм и обращения к функциям, соответственно. Пусть каждая из этих функций использует ассоциативный список для хранения связанных имен — значений переменных и определений функций.

Сначала этот список пуст.

Вернемся к синтаксической сводке вычислимых форм.

форма ::= переменная
             | (QUOTE S-выражение)
             | (COND (форма форма) ... (форма форма))
             | (функция аргумент ...)

аргумент ::= форма

переменная ::= идентификатор

функция ::= название
                 | (LAMBDA список_переменных форма)
                 | (LABEL название функция)
список_переменных ::= (переменная ... )

название ::= идентификатор

идентификатор ::= атом

Ветвям этой сводки будут соответствовать ветви универсальной функции:

Кроме того работа с идентификаторами использует ассоциативный список для хранения связанных имен - значений переменных и определений функций.

(DEFUN EVAL (e) (eval-a e '((NIL . NIL)(T . T))))

Вспомогательная функция APPLY понадобилась для выделения обращений к функциям. Вспомогательная функция EVAL-A понадобилась, чтобы для EVAL завести накапливающий параметрассоциативный список , в котором будут храниться связи имен переменных с их значениями и названий функций с их определениями. Здесь его значение ((NIL . NIL)(T . T)) обеспечивает, что атомы NIL и T обозначают сами себя.

(DEFUN eval-a(e a)
    (COND
        ((ATOM e) (CDR(assoc e a)) )
        ((EQ (CAR e) 'QUOTE) (cadr e))
        ((EQ (CAR e) 'COND) (evcon (CDR e) a))
        ( T (apply (CAR e) (evlis (CDR e) a) a) )
)   )

(defun apply (fn x a)
   (COND 
       ((ATOM fn)

            (COND
               ((EQ fn 'CAR) (caar x))
               ((EQ fn 'CDR) (cdar x))
               ((EQ fn 'CONS) (CONS (CAR x)(cadr x)) )
               ((EQ fn 'ATOM) (ATOM (CAR x)) )
               ((EQ fn 'EQ) (EQ (CAR x)(cadr x)) )
               (T (apply (eval-a fn a) x a))
       )    )
   
     ((EQ (CAR fn)'LAMBDA) (eval-a (caddr fn) 
                                        (pairlis (cadr fn) x a) ))
     ((EQ (CAR fn) 'LABEL) (apply (caddr fn) x 
                                       (CONS(CONS(cadr fn) (caddr fn)) a)
)  ) )                             )

ASSOC и PAIRLIS уже определены ранее.

(DEFUN evcon (c a) 
    (COND 
       ((eval-a (caar c) a) (eval-a (cadar c) a) )
       ( T (evcon (CDR c) a) ) 
)  )

(Не допускается отсутствие истинного предиката, т.е. пустого C.)

(DEFUN evlis (m a)
   (COND
       ((null m) NIL )
       ( T (CONS(eval-a (CAR m) a) 
                     (evlis(CDR m) a) 
)  )  )    )

При

(DEFUN EVAL (e) (eval-a e ObList ))

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

Поясним ряд пунктов этих определений.

Аргумент EVAL — форма. Если она — атом, то этот атом может быть только именем переменной, а значение переменной должно уже находиться в ассоциативном списке.

Если CAR от формы — QUOTE, то она представляет собой константу, значение которой выделяется как CADR от нее самой.

Если CAR от формы — COND, то форма — условное выражение. Вводим вспомогательную функцию EVCON (определение ее будет дано ниже), которая обеспечивает вычисление предикатов (пропозициональных термов) по порядку и выбор формы, соответствующей первому предикату, принимающему значение "истина". Эта форма передается EVAL для дальнейших вычислений.

Все остальные случаи рассматриваются как список из функции с аргументами, который обрабатывается функцией APPLY.

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

Первый аргумент APPLYфункция. Если она — атом, то существует две возможности. Атом может представлять одну из элементарных функций (CAR CDR CONS ATOM EQ). В таком случае соответствующая ветвь вычисляет значение этой функции на заданных аргументах. В противном случае, этот атомназвание ранее заданного определения функции. Определение можно найти в ассоциативном списке , подобно вычислению переменной.

Если функция начинается с LAMBDA, то ее аргументы попарно соединяются со связанными переменными, а тело определения (форма из лямбда-выражения) передается как аргумент функции EVAL-A для дальнейшей обработки.

Если функция начинается с LABEL, то ее название и определение соединяются в пару, и полученная пара размещается в ассоциативном списке, чтобы имя функции стало определенным при дальнейших вычислениях. Они произойдут как рекурсивный вызов APPLY, которая вместо имени функции теперь работает с ее определением при более полном ассоциативном списке — в нем уже размещено определение имени функции. Поскольку определение размещается "наверху" стека, оно становится доступным для всех последующих использований, то есть работает как локальный объект. Глобальные объекты, которые обеспечиваются псевдо-функцией DEFUN, устроены немного иначе, что будет рассмотрено в лекциях 5 и 6.

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

  1. В строгой теории аппликативного программирования все функции следует определять всякий раз, когда они используются. На практике это неудобно. Реальные системы имеют большой запас встроенных функций (более тысячи в Clisp-е), известных языку, и возможность присоединения такого количества новых функций, какое понадобится. Во многих реализациях Лиспа все элементарные функции вырабатывают результат и на списках, и на атомах, но его смысл зависит от системных решений, что может создавать трудности при переносе программ на другие системы. Базисный предикат EQ всегда имеет значение, но смысл его на неатомарных аргументах будет более ясен после знакомства со структурами данных, используемыми для представления списков в машине.
  2. В чистом языке Лисп базисные функции CAR и CDR не определены для атомарных аргументов. Такие функции, имеющие осмысленный результат не на всех значениях естественной области определения, называют частичными. Отладка и применение частичных функций требует большего контроля, чем работа с тотальными, всюду определенными функциями. Во многих реализациях функциональных языков программирования все функции всегда вырабатывают значение. При необходимости каждый существенный класс объектов пополняется значением класса ERROR, символизирующим исключительные ситуации.
  3. Для функциональных языков характерно большое разнообразие условных форм, конструкций выбора, ветвлений и циклов, практически без ограничений на их комбинирование. Форма COND выбрана для начального знакомства как наиболее общая. За редким исключением в Лиспе нет необходимости писать в условных выражениях (QUOTE T) или (QUOTE NIL). Вместо них используются встроенные константы T и NIL, соответственно.
  4. В реальных системах функционального программирования обычно поддерживается работа с целыми, дробными и вещественными числами в предельно широком диапазоне, а также работа с битовыми и текстовыми строками. Такие данные, как и атомы, являются минимальными объектами при обработке информации, но отличаются от атомов тем, что их смысл задан непосредственно их собственным представлением. Их понимание не требует ассоциаций или связывания. Поэтому и константы такого вида не нуждаются в префиксе в виде апострофа.

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

Предикаты и истинность в Лиспе

Хотя формальное правило записи программ вычислений в виде S-выражения предписывает, что константа Т — это (QUOTE T), было оговорено, что в системе всегда пишется Т. Кроме того, NIL оказался удобнее, чем атом F, встречавшийся в начальных предложениях по Лиспу аналог значения FALSE. Программист может либо принять это правило на веру, либо изучить следующие уточнения.

В Лисп есть два атомных символа, которые представляют истину и ложь, соответственно. Эти два атома — T и NIL. Данные символы — действительные значения всех предикатов в системе. Главная причина в удобстве кодирования. Во многих случаях достаточно отличать произвольное значение от пустого списка. Если атомы T и F имеют значение T и NIL, соответственно, то символы T и F в качестве константных предикатов могут работать, потому что:

(eval-a T '((NIL . NIL)(T . T) (F . NIL)))    ; = T
(eval-a NIL'((NIL . NIL)(T . T) (F . NIL)))  ; = NIL
(eval-a F '((NIL . NIL)(T . T) (F . NIL)))    ; = NIL

Формы (QUOTE T) и (QUOTE NIL) будут также работать, потому что:

(EVAL (QUOTE T) )     ; = T
(EVAL (QUOTE NIL) )  ; = NIL

Но

(EVAL (QUOTE F)NIL)  ; = F

Это неправильно, отлично от NIL, и поэтому (QUOTE F) не будет работать как представление ложного значения в системе.

Заметим, что

(EVAL  T )    ; = T
(EVAL NIL )  ; = NIL 
(EVAL F)      ; = NIL

будет работать в силу причин, которые объясняются в лекции 6.

Формального различия между функцией и предикатом в Лиспе не существует. Предикат может быть определен как функция со значениями либо T либо NIL. Это верно для всех предикатов системы. Можно использовать форму, не являющуюся предикатом там, где требуется предикат: предикатная позиция условного выражения или аргумент логической операции. Семантически любое S-выражение, отличное от NIL, будет рассматриваться в таком случае как истинное. Первое следствие из этого — предикат NULL и логическое отрицание NOT идентичны. Второе — то, что (QUOTE T) или (QUOTE Х) практически эквивалентны Т как константные предикаты.

Предикат EQ ведет себя следующим образом:

  1. Если его аргументы различны, значением EQ является NIL.
  2. Если оба его аргументы являются одним и тем же атомом, то значение — Т.
  3. Если значения одинаковы, но не атомы, то его значение T или NIL в зависимости от того, идентично ли представление аргументов в памяти.
  4. Значение EQ всегда T или NIL. Оно никогда не бывает не определено, даже если аргументы неправильные.

Универсальная функция — это джин, выпущенный из бутылки, т.к. потенциал такой функции ограничен лишь способностями нашего воображения. Пока рассмотрены лишь простейшие, самые очевидные следствия из возможности явно применять и уточнять механизмы символьного представления и определения функций, такие как использование накапливающих параметров, связывание обозначений по схеме знак-смысл в ассоциативном списке как имен переменных со значениями, так и названий функций с определениями, а также применение вспомогательных функций для достижения прозрачности определений. Эти возможности имеют место в любом языке высокого уровня. Но попутно выполнено достаточно строгое построение совершенно формальной математической системы, называемой "Элементарный ЛИСП". Составляющие этой формальной системы следующие:

  1. Множество символов, называемых S-выражениями.
  2. Система функциональных обозначений для основных понятий, необходимых при программировании обработки S-выражений.
  3. Формальное представление функциональных обозначений в виде S-выражений.
  4. Универсальная функция (записанная в виде S-выражения), интерпретирующая обращение произвольной функции, записанной как S-выражение, к ее аргументам.
  5. Система базовых функций, обеспечивающих техническую поддержку обработки S-выражений, и специальных функций, обеспечивающих управление вычислениями.

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

< Лекция 2 || Лекция 3: 1234 || Лекция 4 >
Дарья Федотова
Дарья Федотова
Сергей Березовский
Сергей Березовский

В рамках проф. переподготовки по программе "Программирование"

Есть курсы, которые я уже прошел. Но войдя в курс я вижу, что они не зачтены (Язык Ассемблера и архитектура ЭВМ, Программирование на С++ для профессионалов). Это как?

Алина Ленкова
Алина Ленкова
Россия, Ставрополь, СФ МГУПИ, 2014
Валерий Ромашов
Валерий Ромашов
Россия