Common Lisp/Визначення Функцій в Ліспі

Визначення функцій в Ліспі

ред.

Поряд з примітивними функціями можуть існувати функції, визначені користувачем. Функція викликається набором аргументів і повертає єдине значення. Визначення функції в Ліспі має наступний вигляд:

(DEFUN name (arg1 arg2 ...)
  task1
  task2
  . . . . .)

де name — ім’я функції, arg1, arg2, … — аргументи (параметри). Тіло функції містить послідовність задач. Ключове слово DEFUN виникло з DEfine FUNction.

$ (DEFUN FIRST (lst)
(CAR lst) )
$ (FIRST (q w e r t y))
q
$ (DEFUN THIRD (lst)
  (CADDR lst))

$ (THIRD (q w e r t y))
e

Визначимо функцію NULL, яка розпізнає порожній список. Вона повертає істину, якщо її аргументом є порожній список і хибність в іншому випадку.

$ (DEFUN NULL (obj)
  (EQL obj NIL))
$ (NULL '(q w e r))
F
$ (NULL (CDR '(r)))
T

Нам вже відомі три функції розпізнання: EQL, ATOM, NULL. Функції, які застосовуються для перевірки певних умов та можуть повертати лише два значення – істини чи хиби, називаються предикатами.

Тіло функції складається з послідовності завдань. Завдання можуть бути двох типів: прості та умовними. Будь-яке завдання береться в круглі дужки і може розглядатися як список виразів, які треба проінтерпретувати.

Якщо завдання є атомом або його перший елемент є атомом, то таке завдання називається простим. Наприклад, (CONS ‘NR LST).

Якщо перший елемент списка, який описує завдання не є атомом, то таке завдання називається умовним. Наприклад, ((ATOM lst) (CONS expr lst)).

В умовному завданні перший елемент списку обов’язково є предикатом. Якщо значення предикату NIL, то значення завдання стає рівним NIL і Лісп переходить до виконання наступного завдання. Якщо предикат повертає не NIL, відбувається виконання хвосту списку завдання, а інші завдання ігноруються. Якщо предикат повертає Т, а хвіст завдання порожній, то результатом всієї функції буде T.

Напишемо предикат, який розпізнає списки. Якщо аргументом є список, то предикат повертає істину, інакше — хибність. Функцію LISTP можна проінтерпретувати наступним чином: “Якщо obj є атомом, то повернути NIL, інакше повернути T”.

$ (DEFUN LISTP (obj)
  ((ATOM obj) NIL)
  T)

CL(−)

*** - SYSTEM::%EXPAND-FORM: (ATOM OBJ) should be a lambda expression
$ (DEFUN LISTP (obj)
  ((NULL obj))
  ((ATOM obj) NIL)
  T)

В другій колонці написано предикат LISTP, який розпізнає додатково порожній список (повертає істину). Перше завдання є умовним, хвіст якого порожній. Його можна проінтерпретувати так: перевірити об’єкт obj на порожній список, і якщо він є таким, передати як результат функції істину. Немає потреби писати: ((NULL obj) T), оскільки це те ж саме, що і ((NULL obj)). Останнім завданням цих предикатів є атом Т. Це означає, що якщо жодне з умовних завдань не виконане (лише за цієї умови керування програмою дійде до останнього завдання), то як результат функції повернути Т. Для другого визначення функції LISTP маємо:

$ (LISTP 'tree)
NIL

$ (LISTP '())
T

$ (LISTP '(q w e r t y))
T

Для кращого розуміння роботи тіла функції та простих і умовних завдань розглянемо функцію sm та результати, які вона буде генерувати при певних вхідних значеннях:

$ (DEFUN sm (lst)
  ((NULL lst) 10 1)
  (SETQ b 2)
  ((CDR lst) 12)
  (SETQ b 3))

$ (sm '())
1

$ (sm '(i))
3

$ (sm 'g)
3

$ (sm '(q w e))
12

Як бачимо, після виконання простого завдання керування завжди передається наступному завданню (якщо таке є). Якщо предикат умовного завдання істинний, то виконується його хвіст і повертається результат останнього виразу хвоста.

Вмонтована функція (LIST x1 ... xn) утворює та видає список, елементами якого є x1, ..., xn. Якщо аргументи не задані, результатом буде NIL.

$ (LIST 'a 'b 'c 'd)
(a b c d)

$ (LIST 'a '(b c) 'd)
(a (b c) d)

$ (LIST)
NIL

Напишемо функцію MEMBER, яка має два аргументи: nam - символ та lst - список і яка повинна перевірити чи належить символ списку. Інтуїтивно необхідно порівняти символ з першим елементом списку, потім з другим елементом і так далі. Проблема в такому розв’язку виникає в тому, що ми не знаємо наперед довжини списку. А якщо ми і знаємо цю довжину, і якщо вона велика, то тіло функції буде дуже великим. Така функція буде мати приблизно такий вигляд (перший стовпчик):

$ (DEFUN MEMBER (nam lst)
  ((EQL nam (FIRST lst)))
  ((EQL nam (SECOND lst)))
  ((EQL nam (THIRD lst)))
  ((EQL nam (THIRD (CAR lst))))
;; І так далі...

$ (DEFUUN MEMBER (nam lst)
  ((NULL lst) NIL)
  ((EQL nam (CAR lst)) T)
  (MEMBER nam (CDR lst)) )
;; І так далі...


Змінимо наш підхід до побудови функції. В другому стовпчику побудовано функцію MEMBER, в основі якої лежить рекурсивний підхід, який базується на наступних фактах:

  1. Якщо список порожній (не має елементів), то nam не належить списку.
  2. Якщо nam дорівнює голові списку, то nam належить списку.
  3. Якщо nam не дорівнює голові списку, то nam може належити списку тоді і тільки тоді коли nam належить хвосту списку.

Розглянемо дві рекурсивні функції: REMBER (REMove memBER), яка має два аргументи — атом obj та список lst і яка видаляє перше зустрічання атома obj в списку lst. REMBER-ALL яка видаляє всі атоми obj в списку lst.

$ (DEFUN REMBER (obj lst)
  ((NULL lst) NIL)
  ((EQL obj (CAR lst)) (CDR lst))
  (CONS (CAR lst)
	(REMBER obj (CDR lst))))
(DEFUN REMBER-ALL (obj lst)
  ((NULL lst) NIL)
  ((EQL obj (CAR lst))
   (REMBER-ALL obj (CDR lst))
   (CONS (CAR lst)
	 (REMBER-ALL obj (CDR lst)))))

Результат роботи цих функцій проілюструємо на прикладах:

$ (REMBER 'a '(q a w e r t a y))
(q w e r t a y)

$ (REMBER-ALL 'a '(q a w e r t a y))
(q w e r t y)

Примітивна функція EQL використовується для порівняння атомів. Часто виникає потреба порівнювати списки. Напишемо функцію EQLIST, яка порівнює списки. Її побудуємо на основі наступних фактів:

  1. Якщо перший список порожній, то, якщо і другий список порожній, повернути Т, інакше повернути NIL (або просто повернути (NULL другого списку)).
  2. Якщо другий список порожній, повернути NIL.
  3. Якщо голова першого списку не дорівнює голові другого списку, повернути NIL.
  4. Перевірити рівність хвостів першого та другого списків.
<syntaxhighlight lang="lisp">
(DEFUN NOT (obj)
  (EQL obj NIL))

Функція NOT повертає NIL, якщо список не порожній і Т інакше.

Розглянемо задачу об’єднання списків. Роботу функції APPEND, аргументами якої є два списки lst1 та lst2, можна описати наступним чином:

  1. Якщо lst1 порожній, повернути lst2.
  2. З’єднати голову першого списку зі списком, який отримано в результаті об’єднання хвоста першого списку з другим списком.
(DEFUN APPEND (lst1 lst2)
  ((NULL lst1) lst2)
  (CONS (CAR lst1) (APPEND (CDR lst1) lst2)))

Функція (REVERSE lst1) обертає список lst1. Якщо вихідний список порожній, то і результатом буде порожній список. Інакше необхідно об’єднати обернений хвіст вихідного списку з його першим елементом. Оскільки на вхід другого аргумента функції APPEND повинен подаватися список, необхідно з першого елемента списку зробити список, який складається лише з нього. Це виконує команда (CONS (CAR lst) NIL).

(DEFUN REVERSE (lst)
  ((NULL lst) NIL)
  (APPEND (REVERSE (CDR lst))
          (CONS (CAR lst) NIL)))

Напишемо функцію REVERSE без використання функції APPEND. Для цього побудуємо функцію REVERSE з двома аргументами на принципі обробки стеку. Вихідний список — стек символів. Якщо він порожній, то і результуючий стек буде порожнім. Інакше взяти символ з вершини стеку і покласти його на другий стек. Другий стек при виклику повинен бути NIL: (REVER list NIL).

$ (DEFUN REVER (lst1 lst2)
((NULL lst1) lst2)
(REVER (CDR lst1) (CONS (CAR lst1) lst2)) )

$ (REVER '(q w e) NIL)
(e w q)

Середовище системи muLisp

ред.

Середовище muLisp або поточний стан системи складається з усіх активних на даний момент структур даних, значень змінних та визначених функцій. Команда SAVE зберігає поточне середовище muLisp у вигляді SYS - файлу. Команда (SAVE ’C:HOME) зберігає середовище в файл HOME.SYS на диску C. Після успішного виконання команди запису повертається Т, інакше — NIL.

Середовище muLisp може бути завантажене за допомогою команди LOAD: (LOAD <file>). Якщо файл не знайдено, повертається NIL, інакше жодне значення не повертається, а mulisp починає працювати з новим середовищем.

Трасировка функцій в muLisp.

ред.

Мова програмування muLisp для трасировки має програму debug.lsp, яка загружається в середовище Ліспу. Для того, щоб дозволити трасировку будь-якої функції необхідно дати команду (TRACE-FUNCTION <func>). Якщо після цього викликати функцію function, то на екрані відобразиться шлях виконання функції. Команда (UNTRACE-FUNCTION <func>) забороняє трасировку функції <func>. Якщо в тілі функції <func> існує виклик інших функцій, і ми хочемо побачити їх трасировку, необхідно дозволити їх трасировку. Вираз <func>=<value> в трасі означає те, що функція <func> повертає значення <value>.

Наприклад, після команд (TRACE-FUNCTION APPEND) (APPEND ‘(q w e) (r t y u)) на екрані відобразиться траса (спочатку перший стовпчик, потім — другий):

APPEND [(q w e) (r t y u)]APPEND = (r t y u)

APPEND [(w e) (r t y u)] APPEND = (e r t y u)

APPEND [(e) (r t y u)]APPEND = (w e r t y u)

APPEND [NIL (r t y u)]APPEND = (q w e r t y u)


Розглянемо трасу функції REVERSE з дозволом трасировки функції APPEND для виразу (REVERSE ‘(q w)) (спочатку перший стовпчик, потім — другий):


REVERSE [(q w)]REVERSE = (w)

REVERSE [(w)]APPEND [(w), (q)]

REVERSE [NIL]APPEND [NIL, (q)]

REVERSE = NILAPPEND = (q)

APPEND [NIL, (w)]APPEND = (w q)

APPEND = (w)REVERSE = (w q)

Завдання

ред.
  1. Написати функцію, яка знаходить:
    1. третій елемент четвертого підсписку
    2. перший елемент другого підсписку
    3. перший атом списку з підсписками
    4. останній атом лінійного списку
    5. останній атом списку з підсписками
    6. перевіряє належність елемента до списку
    7. Написати функцію REVERSE, не використовуючи функцій селектора та конструктора. Вказівка: використайте функції PUSH та POP.
  2. Написати функцію, яка:
    1. з вихідного списку робить множину
    2. об’єднує дві множини
    3. знаходить різницю двох множин
    4. знаходить перетин двох множин

II Варіант завдань

ред.
  1. Написати функції:
    1. (REVERSE lst), де lst – список з підсписками. Функція повертає обернений на усіх рівнях список lst.
      $ (reverse_all '(1 2 3 (q w e (r t) y) 7 9))
      (9 7 (Y (T R) E W Q) 3 2 1)
      
    2. (FIND_NEIGHBOURS lst node), де lst – список ребер графу (ребро графу є списком з двох чисел – номерів вершин), node – номер вершини. Функція повинна повернути список вершин, суміжних з вершиною node. Граф вважати неорієнтованим.
      $ (find_neighbours '((1 2) (3 1) (4 5) (9 1) (2 3) (1 5)) 1)
      (2 3 9 5)
      
    3. (LINER lst), де lst – список з підсписками. Лінеризувати список lst.
      $ (liner '(3 (q w e r (t) () y ) 4 (5) o (p () )))
      (3 Q W E R T Y 4 5 O P)
      
    4. (SYMDIFF lst1 lst2), де lst1 та lst2 – множини. Повернути їх симетричну різницю.
      $ (symdiff '(2 3 4 5) '(3 41 1 5))
      (2 4 41 1)
      

Відповіді

ред.