Новосибирский Государственный Университет
Опубликован: 05.02.2007 | Доступ: свободный | Студентов: 2223 / 413 | Оценка: 4.30 / 4.23 | Длительность: 10:15:00
Лекция 5:

Определение языка программирования

< Лекция 4 || Лекция 5: 12 || Лекция 6 >
Аннотация: Теперь можем дать более точное определение Лиспа как языка программирования и показать методы программирования на Лиспе. Рассмотрим синтаксис языка Лисп и его семантику. Познакомимся с техникой накопительных параметров, позволяющей при программировании обходиться без глобальных переменных, и методом вспомогательных функций, обеспечивающим управление уровнем абстрагирования информационной обработки.

Начнем с синтаксического обобщения.

Определение языка программирования обычно начинают с синтаксических формул, называемых БНФ ( формулы Бекуса-Наура ). Определение таких формул сводится к следующим положениям:

  • Язык характеризуется набором определяемых понятий.
  • Каждому понятию соответствует набор вариантов синтаксических формул.
  • Каждый вариант – это последовательность элементов.
  • Элемент – это или терминальный символ или синтаксическое понятие – нетерминальный символ.

Синтаксис данных в Лиспе сводится к правилам представления атомов и S-выражений.

<атом> ::= <БУКВА> <конец_атома>

<конец_атома> ::= <пусто> 
         | <БУКВА> <конец_атома> 
         | <цифра> <конец_атома>

В Лиспе атомы - это мельчайшие частицы. Их разложение по литерам обычно не имеет смысла.

<S-выражение> ::= <атом>
         | (<S-выражение> . <S-выражение>)  ; пара
         | (<S-выражение> ... )             ; список

По этому правилу S-выражения - это или атомы, или узлы из пары S-выражений, или списки из S-выражений.

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

Символ " ; " - начало комментария до конца строки.

Т.о. " () " есть допустимое S-выражение. Оно в языке Лисп по соглашению эквивалентно атому Nil.

Базовая система кодирования данных - точечная нотация, хотя на уровне текста запись в виде списков удобнее. Любой список можно представить точечной нотацией:

() = Nil 
 (a . Nil) = (a)
  - - - 
 (a1 . ( ... (aK . Nil) ... )) = (a1 ... aK)

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

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

Синтаксис программ в Лиспе внешне не отличается от синтаксиса данных. Просто выделяем вычислимые выражения (формы), т.е. данные, приспособленные к вычислению. Внешне это выглядит как объявление объектов, заранее известных в языке, и представление разных форм, вычисление которых обладает определенной спецификой.

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

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

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

<форма> ::= <константа>
   | <переменная>
   | (COND (<форма> <форма>) (<форма> <форма>) ... )
   | (<функция> <аргумент> ... )

<константа> ::= (QUOTE <S-выражение>)
       | '<S-выражение>

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

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

Форма - это выражение, которое может быть вычислено. Формами являются переменные и списки, начинающиеся с QUOTE, COND или с представления некоторой функции.

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

Если форма представляет собой константу, то нет необходимости в вычислениях, независимо от вида константы. Константные значения, могут быть любой сложности, включая вычислимые выражения, но в данный момент они не вычисляются. Константы изображаются с помощью специальной функции QUOTE, блокирующей вычисление. Представление констант с помощью QUOTE устанавливает границу, далее которой вычисление не идет. Использование апострофа (') - просто сокращенное обозначение для удобства набора текста. Константные значения аргументов характерны при тестировании и демонстрации программ.

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

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

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

Последнее правило задает формат условного выражения. Согласно этому формату условное выражение строится из размещенных в двухэлементном списке синтаксически различимых позиций для условий и обычных форм. Двухэлементные списки из определения условного выражения рассматриваются как представление предиката и соответствующего ему S-выражения. Значение условного выражения определяется перебором предикатов по порядку, пока не найдется форма, значение которой отлично от Nil, что означает логическое значение "истина". Строго говоря, такая форма должна найтись непременно. Тогда вычисляется S-выражение, размещенное вторым элементом этого же двухэлементного списка. Остальные предикаты и формы условного выражения не вычисляют (логика Мак-Карти), их формальная корректность или определенность не влияют на существование результата.

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

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

<список_переменных> ::= (<переменная> ... )

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

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

Таким образом, функция - это или название, или список, начинающийся с LAMBDA или DEFUN.

Функция может быть представлена просто именем. В таком случае ее смысл должен быть заранее известен. Например, встроенные функции CAR, CDR и т.д.

Функция может быть введена с помощью лямбда-конструктора, устанавливающего соответствие между аргументами функции и связанными переменными, входящими в тело ее определения (в определяющей ее форме). Форма из определения функции может включать переменные, не включенные в лямбда-список, - так называемые свободные переменные. Их значения должны устанавливаться на более внешнем уровне. Если функция рекурсивна, то следует объявить ее имя с помощью специальной функции DEFUN.

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

Начнем с общих методов обработки S-выражений.

AMONG – проверка входит ли заданный атом в данное S-выражение.

(DEFUN among  (x y) (COND 
   ((ATOM y) (EQ x y))
   ((among x (CAR y)) (QUOTE T))
   ((QUOTE T) (among x (CDR y) ))
       )
)

(among  'A '( B . A )  )

EQUAL - предикат, проверяющий равенство двух S-выражений. Его значение "истина" для идентичных аргументов и "ложь" для различных. (Элементарный предикат EQ строго определен только для атомов.) Определение EQUAL иллюстрирует условное выражение внутри условного выражения (двухуровневое условное выражение и двунаправленная рекурсия)

(DEFUN equal (x y) (COND 
    ((ATOM x) (COND 
    ((ATOM y) (EQ x y))
    ((QUOTE T) (QUOTE NIL))
        )
      )
((ATOM y) (QUOTE NIL))
  ((equal (CAR x)(CAR y)) (equal (CDR x)(CDR y)))
 ((QUOTE T) (QUOTE NIL))
      )
)

(equal '( A B ) '( A . B))
(equal '( A B ) '( A . (B . Nil)) )

При желании можно дать название этой функции по-русски:

(DEFUN равно_ли  (x y)  (equal x y))

SUBST - функция трех аргументов x, y, z, строящая результат замены S-выражением x всех вхождений y в S-выражение z.

(DEFUN subst (x y z) (COND 
         ((equal y z) x)
         ((ATOM z) z)
         ((QUOTE T)(CONS 
             (subst x y (CAR z)) 
             (subst x y (CDR z))
                )
          )
                  )
)

(subst '(x . A) 'B '((A . B) . C))          ;= ((A . (x . A)) . C)
(subst 'x '(B C D) '((A B C D)(E B C D)(F B C D))) ;= ((A . x)(E . x)(F . x))

Символ " ; " - начало комментария.

Использование equal в этом определении позволяет подстановку осуществлять и в более сложных случаях. Например, для редукции совпадающих хвостов подсписков.

(DEFUN Подстановка (x y z) (subst x y z))
(Подстановка '(x . A) 'B '((A . B) . C))        ;= ((A . (x . A)) . C)

NULL - предикат, отличающий пустой список от всех остальных S-выражений. Используется, чтобы выяснять, когда список исчерпан. Принимает значение "истина" тогда и только тогда, когда его аргумент - Nil.

(DEFUN null (x) (COND 
            ((EQ x (QUOTE Nil)) (QUOTE T))
            ((QUOTE T)  (QUOTE Nil))
          )
)
( null '() )

При необходимости можно компоненты точечной пары разместить в двухэлементном списке и наоборот, из первых двух элементов списка построить в точечную пару.

(DEFUN pair_to_list  (x)  (CONS (CAR x) (CONS (CDR x) Nil)) )
( pair_to_list  '( A . B ) )

(DEFUN list_to_pair  (x)  (CONS (CAR x) (CADR x)) )
(list_to_pair  '( A B)  )

По этим определениям видно, что списочная запись строится большим числом CONS, т.е. на нее расходуется больше памяти.

< Лекция 4 || Лекция 5: 12 || Лекция 6 >