В статье рассматриваются формы вызова функций в Лисп-системах.
Автор: В.Водолазкий
Написал: artish   Дата: 2008-09-08 20:44
Комментарии: (0)   Рейтинг:
Пока комментариев нет

Вызов функций

 

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

apply function arg &amps;rest more-args

Задача apply состоит в применении функции function к списку аргументов. При этом функция может представлять собой скомпилированный объект кода, представлять собой лямбда-выражение или символ.

 В последние годы в ряде реализаций Common Lisp не допускается использовать в качестве аргумента apply старые добрые лямбда-выражения. Однако это ограничение легко обходится - достаточно использовать аббревиатуру

#'
перед началом выражения, которое введено в явном виде, и все будет в порядке.

Аргументы, передаваемые function образуются из последнего аргумента apply, которые с помощью append дописываются в конец вызова функции. Фактически, создается список аргументов, который и передается функции для обработки. Например:

(setq f '+) (apply f '(1 2))        ; 3 
(setq f #'-) (apply f '(1 2))       ; -1 
(apply  #'max 3 5 '(2 7 3))         ; 7 
(apply 'cons '((+ 2 3) 4))          ; ((+ 2 3) . 4)  а не (5 . 4) 
(apply #'+ '())                     ; 0

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

(apply #'(lambda (\cd{\&key} a b) (list a b)) '(:b 3)) ; (nil 3)

Это может оказаться весьма полезным при использовании режима &allow-other-keys:

(defun foo (size &rest keys &key double &allow-other-keys) 
  (let ((v (apply #'make-array size :allow-other-keys t keys))) 
     (if double (concatenate (type-of v) v v) v))) 
 
(foo 4 :initial-contents '(a b c d) :double t) 
                ; ->  #(a b c d a b c d)

funcall fn &rest arguments

Функция (funcall fn a1 a2 ... an) применяет функцию fn к аргументам a1, a2, ..., an. При этом fn не может представлять собой макрос или специальную форму, поскольку никакого смысла в таком вызове просто быть не может.

Так же, как и в случае apply, обработка лямбда-выражений осуществляется с помощью специальной аббревиатуры #', которая представляет собой сокращенное описание вызова function.

Например:

(cons 1 2) ==> (1 . 2) 
(setq cons (symbol-function '+)) 
(funcall cons 1 2) ==> 3

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

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

Константа call-arguments-limit

Значение константы call-arguments-limit представляет собой положительное число, представляющее собой максимальное количество аргументов, которое может быть передано функции. Это значение зависит от реализации системы и, например, для GNU Common Lisp оно равно 64. Значение этой переменной должно быть не меньше lambda-parameters-limit (в GCL также равна 64).

Последовательные вычисления

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

Специальная форма progn

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

Форма progn представляет собой одну из элементарных управляющих структур, предназначенных для формирования так называемых ``составных операторов'', аналогами которых в процедурных языках программирования являются, например, блоки begin-end.

Стоит заметить, что многие из конструкций Лиспа на самом деле представляют собой неявные progn-формы, поскольку их синтаксис позволяет включить в качестве аргументов несколько (или вернее сказать, произвольное количество) форм, значения которых затем вычисляются последовательно, одна за другой, а затем в качестве результата используется значение только последней формы.

Если последняя форма, входящая в вызов progn возвращает несколько значений, то и сама форма progn возвращает все эти значения. Если внутри блока progn не оказалось ни одной формы, то в качестве результата будет возвращено NIL. Эти правила верны и для неявных вызовов progn.

Функция prog1

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

Как правило, prog1 используется для оценки значений выражений с побочными эффектами, в которых интерес представляет результат, полученный {\it до того}, как эти побочные эффекты вступят в силу. Например:

(prog1 (car x) (rplaca x 'foo))
изменяет car-часть ячейки x, делая ее равной foo и возвращает прежнее значение car-части x.

Функция prog1 всегда возвращает только одно значение, даже если сама первая форма, являющаяся ее аргументом, формирует сразу несколько. В результате

(prog1  x)
и
(progn  x)
могут вести себя по-разному, если x приводит к получению нескольких значений.

Хотя prog1 может принудительно возвращать одно единственное значение, обычно в Common Lisp для этой цели используется функция values.

Функция prog2

Функция prog2 также похожа на prog1, но возвращает значение своей второй формы. Все аргументы оцениваются последовательно, один за другим, при этом результат вычисления второй формы запоминается и возвращается как результат всей функции. В современных программах prog2 используется редко, но тем не менее входит в состав стандартных библиотек функций для обеспечения совместимости со старым программным обеспечением. Как видно из приведенного ниже фрагмента, сама по себе эта функция просто является избыточной.
(prog2  a b c ...  z)  =  (progn  a (prog1 b  c ...  z))

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

(prog2 (open-a-file) (process-the-file) (close-the-file)) 
   ;значение функции --- process-the-file
Функция prog2, как и prog1, всегда возвращает одно значение, даже в том случае, если вторая форма пытается вернуть сразу несколько. В результате
(prog2  x  y)
и
(progn  x  y)
могут вести себя по-разному, если y возвращает несколько значений.

Установление новых связей переменных

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

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

Специальная форма let

let (var | (var [value])*) declaration* form*  

Форма let предназначена для последовательного выполнения нескольких форм, в которых осуществляется связывание заданных переменных с вычисляемыми значениями.

С формальной точки зрения, форма вида

(let ((var1 value1) 
      (var2 value2) 
      ... 
      (varm valuem)) 
  declaration1 
  declaration2 
  ... 
  declarationp 
  body1 
  body2 
  ... 
  bodyn)
вначале оценивает значения выражений value1, value2, и т.д., и сохраняет полученные результаты. Затем все переменные varj одновременно связываются с соответствующими результатами вычислений; при этом каждое связывание представляет собой лексическую связь, если только иное не оговаривается явно с помощью объявления special.

Затем последовательно вычисляются значения bodyk; при этом игнорируются все полученные значения, кроме последнего (Это означает, что тело let представляет собой неявную конструкцию progn.). Форма let возвращает результат оценки значения bodyn (если тело пусто, что с практической точки зрения просто не имеет смысла, то let возвращает в качестве значения NIL).

Допускается вместо списка ( varj valuej), использовать просто запись varj. В этом случае varj вначале присваивается значение равное NIL. В соответствии с правилами хорошего тона рекомендуется использовать varj только в тех случаях, когда значение этой переменной до своего первого использования будет сохранено, например, с помощью setq. И если для правильной работы алгоритма необходимо, чтобы начальное значение было именно NIL, то рекомендуется откровенно указывать --- (varj NIL), если начальное значение должно означать ``false,''или (varj '()), если начальное значение должно представлять собой пустой список.

Например, фрагмент кода

(let (x) 
  (declare (integer x)) 
  (setq x (gcd y z)) 
  ...)

содержит ошибку --- несмотря на то, что x в самом деле получает значение до того, как будет использовано, при этом оно должно иметь тип integer, а поскольку при инициализации x моментально получает значение NIL, это приводит к к тому, что фиксируется нарушение типа переменной --- ведь NIL не является целым числом...

Объявления типов переменных могут располагаться только в начале тела формы let.

Специальная форма let*

let* (var | (var [value])*) declaration* ,form

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

Иначе говоря, форма

(let* ((var1 value1) 
       (var2 value2) 
       ... 
       (varm valuem)) 
  declaration1 
  declaration2 
  ... 
  declarationp 
  body1 
  body2 
  ... 
  bodyn)
вначале оценивает значение value1, затем связывает с этим значением переменную var1; после этого оценивается значение value2 и результат связывается с var2, затем процесс продолжается...

После этого осуществляется последовательное вычисление выражений bodyj. Все полученные значения кроме последнего (bodyn) отбрасываются (Это означает, что тело let представляет собой неявную конструкцию progn.). Форма let* возвращает результат оценки bodyn (если ни одной формы не вызывалось, let* возвращает NIL).

Допускается вместо списка ( varj valuej), использовать просто запись varj. В этом случае varj вначале присваивается значение равное NIL. В соответствии с правилами хорошего тона рекомендуется использовать varj только в тех случаях, когда значение этой переменной до своего первого использования будет сохранено, например, с помощью setq. И если для правильной работы алгоритма необходимо, чтобы начальное значение было именно NIL, то рекомендуется откровенно указывать --- ( varj NIL), если начальное значение должно означать ``false,''или ( varj '()), если начальное значение должно представлять собой пустой список.

Вначале области тела let* могут помещаться объявления типа переменных.

Специальная форма compiler-let

compiler-let (var | (var [value])*) ,form*

В тех случаях, когда compiler-let выполняется интерпретатором Лиспа, эта функция ведет себя аналогично let, включая неявные объявления special для связей переменных. Однако если эта форма обрабатывается компилятором, то код для вычисления значений переменных не генерируется. Вместо этого, обработка тела компилятором (включая все расширения макросов в теле) выполняются с использованием специальных переменных, ассоциируемых с используемыми в форме значениями, назначенным введенным переменным. Основное назначение этой формы состоит в упрощении организации взаимодействия между сложными макросами.

В теле этой формы не допускается использование объявлений типа.

Во многих современных реализациях CommonLisp эта форма отсутстствует. Вместо нее рекомендуется использовать macrolet или symbol-macrolet.

Специальная форма progv

progv symbols values ,form*
Форма progv позволяет выполнять связывание одной или нескольких переменных, имена которых определяются во время выполнения программы. При этом оцениваются значения последовательности форм (неявный progn) которые присваиваются динамическим переменным, имена которых находятся в списке символов symbolsв соответствии со списком values.

Если форма содержит слишком мало значений, то все оставшиеся символы не получают никакого значения; см. makunbound. Если передано слишком много значений, то все лишние игнорируются.

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

Форма progv особенно полезна при написании интерпретаторов на Лиспе и предоставляет программисту удобный механизм для связывания динамических переменных.

Специальная форма flet

flet ((name lambda-list
         ,form*)*)
     ,form* 
labels ((name lambda-list
           ,form*)*)
       ,form* 
macrolet ((name varlist
             ,form*)*)
         ,form*

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

Можно определить произвольное количество функций. При этом каждое определение по формату подобно форме defun form: вначале задается имя, затем список параметров (который может содержать ключи &optional, &rest, или &key), затем следуют необязательные объявления типов и строки документирования, и наконец, собственно тело функции.

Например:

(flet ((safesqrt (x) (sqrt (abs x)))) *
  ;; Функция safesqrt используется дважды. *
  (safesqrt (apply #'+ (map 'list #'safesqrt longlist))))

Конструкция labels в основном идентична рассмотренной выше flet. Однако эти конструкции различны с той точки зрения, что имена функций flet влияют только не тело функции, в то время как labels относится к самим определениям этих функций. Это означает, что labels могут использоваться для определения взаимно рекурсивных функций, что невозможно сделать с помощью flet. Именно это различие делает форму labels настолько полезной. Использование flet позволяет локально переопределить имя функции, но новое локальное определение будет ссылаться на глобальное определение; а вот при использовании labels такого эффекта не возникнет. Вот небольшой пример:

(defun integer-power (n k)       ; Функция возведения в степень *
  (declare (integer n))          ; целого числа *
  (declare (type (integer 0 *) k)) 
  (labels ((expt0 (x k a) *
             (declare (integer x a) (type (integer 0 *) k)) *
             (cond ((zerop k) a) *
                   ((evenp k) (expt1 (* x x) (floor k 2) a)) *
                   (t (expt0 (* x x) (floor k 2) (* x a))))) 
           (expt1 (x k a) *
             (declare (integer x a) (type (integer 1 *) k)) *
             (cond ((evenp k) (expt1 (* x x) (floor k 2) a)) *
                   (t (expt0 (* x x) (floor k 2) (* x a)))))) *
    (expt0 n k 1)))

Наконец, форма macrolet также подобна flet, но предназначена для определения локальных макросов, используя точно такой же формат, что и defunro.

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

;;; Пример использования macrolet. *
*
(defun foo (x flag) *
  (macrolet ((fudge (z) *
                ;; Параметры x и flag в этот момент  *
                ;; недоступны; ссылка на flag относится *
                ;;  к глобальной переменной с таким именем. *
                `(if flag 
                     (* ,z ,z) 
                     ,z))) 
    ;; А здесь параметры x и flag доступны. *
    (+ x *
       (fudge x) *
       (fudge (+ x 1)))))

После расширения макроса, определенного с помощью macrolet, мы получим:

(+ x *
   (if flag *
       (* x x) *
       x)) *
   (if flag *
       (* (+ x 1) (+ x 1)) *
       (+ x 1)))

Все упоминания x и flag вполне корректно относятся к параметрам функции foo, поскольку эти параметры видимы макровызову во время его расширения.

Специальная форма symbol-macrolet

symbol-macrolet ((var expansion)*)
                declaration* ,form*

Появление формы symbol-macrolet отражает интеграцию в CommonLisp объектно-ориентированного расширения CLOS, которое мы рассмотрим отдельно, и уж точно не в этом году. Однако symbol-macrolet может использоваться и независимо от CLOS.

Формы forms выполняются как неявный вызов progn в лексическом окружении, которое приводит к тому, что каждая ссылка на любую переменную var, определенную при вызове symbol-macrolet, заменяется соответствующим ей выражением expansion. Можно считать, что ссылка на переменную var интерпретируется как макровызов без параметров; затем либо оценивается значение expansion либо помещается на место ссылки.

Обратите внимание, что имена, создаваемые таким образом, используют пространство имен переменных, но не функций; поэтому вы можете иметь функцию (или макрос, или специальную форму) и одновременно --- переменную с тем же самым именем, и это не приведет ни к каким конфликтам.

Однако это приводит к тому, что результат symbol-macrolet может быть затенен при последующем использовании let или других конструкций, которые осуществляют связывание переменных; symbol-macrolet не выполняет подстановку всех обращений к var вообще, а только тех, которые конструируются как ссылки к переменным в области действия лексической связи var как переменной. Например:

(symbol-macrolet ((pollyanna 'goody)) 
  (list pollyanna (let ((pollyanna 'two-shoes)) pollyanna))) 
;; (goody two-shoes ) -- но не (goody goody)!

Вам может показаться, что 'goody просто заменяет все обращения к символу pollyanna, и значением let будет goody; но не все так просто! В случае такого поведения системы мы получили бы:

(list 'goody (let (('goody 'two-shoes)) 'goody))

что синтаксически неверно. Правильная расширенная форма представляет собой:

(list 'goody (let ((pollyanna 'two-shoes)) pollyanna))

поскольку смена связи pollyanna формой let затеняет символ из макроопределения.

Расширение expansion, ассоциируемое с каждой переменной var оценивается не во время связывания, но только после того, как будет заменено обращение к var. Макрос setf позволяет использовать этот макрос как место применения нового значения, в качестве которого используется расширение этого макроса; кроме того setq для переменной, которая на самом деле является макро-символом, также интерпретируется как применение setf. Форма возвращает значение последней формы, либо nil, если не было получено никакого значения.

Имейте в виду, что использование объявлений типов declarations перед телом хотя и допускается, но может приводить к неожиданным результатам.

Кондиционалы

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

Традиционной для Лисп конструкцией, предназначенной для организации условного выполнения функций является, конечно же cond. Однако более удобной и простой является форма if, которая навевает прямые ассоциации с условными операторами в других языках программирования. Поэтому, в целях соблюдения преемственности с Бейсиком и Паскалем мы начнем обсуждение условных операторов именно с этой формы. Кроме того, мы рассмотрим конструкции case и typecase, которые также покажутся начинающим более удобными чем cond.

Специальная форма if

if test then [else]

Специальная форма if соответствует конструкции if-then-else, реализованной в большинстве современных языков программирования процедурного типа. Вначале осуществляется оценка значения формы test. Если полученный результат отличен от NIL, выполняется форма then; в противном случае выполняется форма else. Сама форма if возвращает в качестве результата значение выполненной формы.

С точки зрения традиционного Лиспа конструкция if эквивалентна:

(if  test  then  else) <=> (cond ( test  then) ({\true  else))

но понятно, что if в некоторых случаях более удобна.

Форма else может отсутствовать, в этом случае если значение test равно NIL, то никаких дополнительных действий не выполняется и значение всей формы if становится равным NIL. Если значение, возвращаемое формой ifв этой ситуации имеет для вас значение, то более предпочтительным может быть использование and. Если же возвращаемое значение роли не играет, то более удобным может оказаться when.

Форма when

when test form*

Форма вида (when test form1 form2 ... ) вначале оценивает значение условия test. Если результат равен NIL, то form не выполняется и возвращается NIL. В противном случае формы form образуют неявный progn и оцениваются последовательно, слева направо. Значение последней формы становится значением всей функции.

(when  p  a  b  c)   ;;  (and  p (progn  a  b  c)) 
(when  p  a  b  c)   ;; (cond ( p  a  b  c)) 
(when  p  a  b  c)   ;; (if  p (progn  a  b  c) NIL) 
(when  p  a  b  c)   ;; (unless (not  p)  a  b  c)

Обычно when используется для условного формирования тех или иных побочных эффектов, а значение, которое возвращает when, в большинстве случаев не используется. Если же возвращаемое значение представляет для вас практический интерес, то более предпочтительно использовать and или if.

Форма unless

unless test ,form*
Форма (unless test form1 form2 ... ) вначале вычисляет значение test. Если результат отличен от NIL, то формы form не оцениваются, и возвращается NIL. В противном случае формы form образуют неявный вызов progn и оцениваются слева направо. Значение последней формы возвращается как значение всей функции.
(unless  p  a  b  c) ;; (cond ((not  p)  a  b  c)) 
(unless  p  a  b  c) ;; (if  p NIL (progn  a  b  c)) 
(unless  p  a  b  c) ;; (when (not  p)  a  b  c)

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

Форма cond


cond (test form*)*



Форма cond может иметь произвольное количество (включая нуль)
вариантов, которые представляют собой список форм. Каждый из
вариантов состоит из условия  test, за которым следует нуль
или больше следствий  consequents.

Например:


(cond ( test-1  consequent-1-1  consequent-1-2 ...) 
      ( test-2) 
      ( test-3  consequent-3-1 ...) 
      ... )

Выполняется первый вариант, в котором test получает значение, отличное от NIL; все остальные варианты игнорируются, а следствия в выбранном варианте выполняются последовательно, образуя неявный вызов progn.

С формальной точки зрения cond последовательно просматривает варианты слева направо. Для каждого варианта производится оценка условия test. Если результатом является NIL, cond переходит к анализу следующего варианта. В противном случае cdr-часть варианта рассматривается как список форм, подлежащих последовательному исполнению, и они оцениваются слева направо, как неявный progn. После завершения выполнения cond завершает работу, даже не приступая к анализу следующих вариантов.

Если для выбранного cond варианта отсутствовал список следствий, то возвращается результат оценки теста. И наконец, если в ходе проверки не удалось найти ни одного подходящего варианта, то значением cond становится NIL.

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

Согласно правилам хорошего счета рекомендуется всегда помещать в конце формы cond вариант (T NIL).

Например:

(setq z (cond (a 'foo) (b 'bar)))              ; Возможно, непонятно... 
(setq z (cond (a 'foo) (b 'bar) (T NIL)))      ; Это лучше 
(cond (a b) (c d) (e))                         ; И это непонятно 
(cond (a b) (c d) ({\true e))                  ; А это лучше 
(cond (a b) (c d) ({\true (values e)))         ; Еще лучше, если нужно  
 				               ; только одно значение 
(cond (a b) (c))                               ; Это тоже непонятно 
(cond (a b) (t c))                             ; И лучше вот так 
(if a b c)                                     ; А еще лучше так

Форму cond в Лиспе можно сравнить с традиционной расширяемой конструкцией if- then- else, используемой в большинстве языков программирования:

(cond ( p ...)                                if  p  then ... 
      ( q ...)                     грубо           else  if  q  then ... 
      ( r ...)                соответствует        else  if  r  then ... 
      ...                                                ... 
      (T ...))                                     else ...

Форма case

case keyform ((key*) | key ,form*)*

Форма case представляет собой кондиционал, который выбирает для выполнения один из вариантов путем сравнения значения формы keyform с различными константами key, которые обычно представляют собой ключевые слова, числа или символы, но можно, вообще говоря, использовать произвольные объекты Лиспа. Формат этой формы следующий:

(case  keyform 
  ( keylist-1  consequent-1-1  consequent-1-2 ...) 
  ( keylist-2  consequent-2-1 ...) 
  ( keylist-3  consequent-3-1 ...) 
  ...)

По своей структуре case во многом похож на cond, да и ведет себя похоже. Однако case отличается в части работы механизма выбора исполняемого варианта.

Первое, что делает case - это оценка значения формы keyform, для получения объекта, который называется ключевым объектом формы. Затем case по очереди рассматривает все варианты. Если при этом ключ key находится в keylist, то есть равен в смысле eql какому либо объекту в keylist данного варианта, то следствия этого варианта оцениваются как неявный progn; case возвращает то, что было возвращено последним следствием либо NIL, если в данном варианте не было ни одного следствия. Если не удается найти ни одного подходящего варианта, то case возвращает NIL.

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

Допускается вместо списка ключей keylist, использовать один из символов --- T или otherwise. Вариант, содержащий такой символ, всегда будет выполняться, а поэтому должен быть размещен в самом конце формы (Это единственное исключение из правила, которое гарантирует независимость порядка следования операторов).

В том случае, когда варианту соответствует один единственный ключ, он может записываться вместо списка, состоящего из одного элемента. При этом никакой неоднозначности в интерпретации ключей не возникает. Такой ключ не может иметь значение NIL (которое может быть интерпретировано как () - список без ключей), T, или представлять собой cons-ячейку.

Форма typecase

typecase keyform (type {,form*)*

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

(typecase  keyform *
  ( type-1  consequent-1-1  consequent-1-2 ...) *
  ( type-2  consequent-2-1 ...) *
  ( type-3  consequent-3-1 ...) 
  ...)

По своей структуре typecase во многом схожа с cond или case, и ведет себя почти так же, выбирая и выполняя один из вариантов. Однако, эта форма отличается от упомянутых выше механизмом выбора варианта.

Прежде всего это отличие typecase, состоит в оценке значения формы keyform, которая должна сгенерировать объект, который называется ключевым объектом. Затем typecase последовательно рассматривает каждый из вариантов. При проверке варианта осуществляется сопоставление полученного типа ключевого объекта со спецификатором типа type, который не оценивается при вызове функции и должен представлять собой литеральный спецификатор типа. Выполняется первый вариант, спецификатор типа которого удовлетворяет типу ключевого объекта. Следствия этого варианта выполняются как неявный вызов progn, и typecase возвращает результат оценки последней формы. Если в данном варианте не было описано ни одной формы, либо если не удалось подобрать ни одного варианта, то typecase возвращает NIL.

Допускается использовать один и тот же спецификатор типа в различных вариантах, например, в тех случаях, когда один из них является подтипом другого; в этом случае выполняется тот, который будет проанализирован раньше. Таким образом, при работе с typecase, в отличие от case, порядок следования вариантов оказывает влияние на поведение всей формы в целом. Например:

(typecase an-object 
   (string ...)              ; Этот вариант обрабатывает строки 
   ((array t) ...)           ; Этот --- массивы общего вида 
   ((array bit) ...)         ; Этот --- битовые массивы 
   (array ...)               ; А этот --- все прочие массивы 
   ((or list number) ...)    ; Обработка списков и чисел 
   (t ...))                  ; И наконец, всех прочих объектов

В некоторых реализациях Common Lisp, компилятор может сгенерировать предупреждение, если вариант никогда не может быть выполнен из за его затенения более ранними вариантами.



Онлайн :

0 пользователь(ей), 6 гость(ей) :




Реклама на сайте: