Funkcje

Trywialne funkcje

Funkcja identity używana jest głównie jako domyślny argument funkcji wyższych rzędów, na przykład operujących na sekwencjach (parameter :key)

(identity obiekt)
Oczywiście zwraca swój argument.

Przykłady

* (identity 'cons)
CONS

* (map 'vector 'identity '(kot pies krowa))
#(KOT PIES KROWA)

* (apply 'identity (list 20))
20

Definiowanie funkcji

Funkcje zwykle definiuje się globalnie używając konstrukcji specjalnej defun. W najprostszej postaci wygląda ona następująco

(defun nazwa-funkcji (parametr ...) 
  wyrażenie ...)

Wartością funkcji jest wartość ostatnio obliczonego wyrażenia treści.

Na przykład funkcję podnoszenia do kwadratu można zdefiniować jako

* (defun kwadrat (n) (* n n))
KWADRAT
* (kwadrat 7)
49

Konstrukcji defun można również użyć wewnątrz innej konstrukcji. Trzeba jednak pamiętać, że takie wewnętrzne wyrażenia defun definiują funkcje globalne, a nie lokalne (tak jak np. define w Scheme). Do definiowania funkcji lokalnych służą konstrukcje labels i flet.

Parametry opcjonalne

Funkcje w Lispie (np. max) moga akceptować zmienną licznę argumentów. Ich zdefiniowanie wymaga użycia w definicji funkcji rozszerzonej postaci listy parametrów. Oprócz zmiennej liczby parametrów dla funkcji można też określać parametry opcjonalne: pozycyjne i kluczowe. Używa się do tego specjalnych znaczników &optional, &keyword i &rest.

Funkcję o zmiennej liczbie argumentów definiujemy następująco

(defun f (x y &rest pozostałe) ...)

Z wszystkich argumentów poza pierwszymi dwoma zostanie zbudowana lista i stanie się wartością pozostałe, np.

* (defun plus (x y &rest reszta) (apply #'+ x y reszta))
PLUS
* (plus 1 2 3)
6

Funkcję z parametrem opcjonalnym można zdefiniować na trzy sposoby:

(defun f (wymagany &optional dodatkowy) ...)
(defun f (wymagany &optional (dodatkowy 1)) ...)
(defun f (wymagany &optional (dodatkowy 1 czy-byl?)) ...)

W drugim przypadku jeśli argument opcjonalny zostanie pominięty w wywołaniu, wartością parametru dodatkowy stanie się 1 (w pierwszym przypadku będzie to nil.

W trzecim przypadku wartością czy-był? stanie się wartość ,,logiczna'' t lub nil, zależnie od tego, czy argument opcjonalny wystąpił w wywołaniu funkcji f.

Parametru opcjonalnego można użyć np. dla określania ,,kroku'' w definicji pochodnej numerycznej:

(defun pochodna (fun x &optional (krok 0.00001))
  (/ (- (funcall fun (+ x krok))
        (funcall fun (- x krok)))
     (* 2 krok)))

Opcjonalne parametry kluczowe różnią się od pozycyjnych tym, że w wywołaniu odpowiadające im argumenty można podawać w dowolnej kolejności. Definiuje się je natomiast podobnie:

(defun f (wymagany &key dodatkowy) ...)
(defun f (wymagany &key (dodatkowy 1)) ...)
(defun f (wymagany &key (dodatkowy 1 czy-byl?)) ...)

W wywołaniu odpowiedni argument trzeba poprzedzić symbolem parametru kluczowego

(f 1 :dodatkowy 2)

Popatrzmy na zmienioną definicję pochodnej:

(defun pochodna (fun x &key (krok 0.00001))
  (/ (- (funcall fun (+ x krok))
        (funcall fun (- x krok)))
     (* 2 krok)))

Wywołuje się ją następująco

(pochodna #'sin 1 :krok 0.001)

Funkcja funcall służy do wywoływania funkcji przekazanych jako argumenty lub stanowiących wartości zmiennych

* (funcall #'+ 3 4)
7

W definicji funkcji parametry opcjonalne muszą wystąpić w następującej kolejności: &optional, &rest, &key. Lista będąca wartością parametru &rest zawiera wszystkie argumenty kluczowe.

Nie należy w definicji funkcji używać równocześnie opcjonalnych parametrów pozycyjnych i kluczowych, ponieważ zwykle prowadzi to do trudnych do wykrycia błędów.

Uwaga: istnieje (choć dość spore) ograniczenie na maksymalną liczbę argumentów funkcji!

Znaczniki dodatkowe

Na liście parametrów funkcji może wystąpić dodatkowy znacznik &allow-other-keys (zwykle na końcu). Powoduje on, że w wywołaniach tej funkcji będzie można umieszczać parametry kluczowe nie występujące w definicji. Zwykle robi się tak wtedy, kiedy dla innej funkcji wywołanej w treści (np. make-array) chcemy mieć możliwość przekazania dowolnych jej parametrów (a czasem może ich być sporo.

(defun moja-funkcja (arg1 &rest keys &key arg2 arg3 &allow-other-keys)
  ...
  (apply #'inna-funkcja keys)
  ...)

Jak widać, argument typu &rest pozwala łatwo przekazać wszystkie parametry kluczowe. Może się jednak zdarzyć, że funkcja wewnętrzna nie będzie akceptować naszych parametrów (np. arg2) --- z pewnością będzie tak dla funkcji standardowych. Można ją jednak do tego przekonać dualnym pseudoparametrem kluczowym :allow-other-keys z wartością t, na przykład

 
  (apply #'make-array ile :allow-other-keys t keys)      

Modyfikatorów tych używa się zwłaszcza w definicjach metod dla funkcji generycznych.

Jakkolwiek symbole nie należą do typu function}, w pewnych sytuacjach są one automatycznie zamieniane na nazwane nimi funkcje (koercja).

* (funcall 'sin 3)
0.14112
* 

Funkcje lokalne

Nierekurencyjne funkcje lokalne definiuje się używając flet:

* (defun f (x)
    (flet ((add-1 (x) (+ x 1)))
      (add-1 x)))
F
* (f 2)
3
* (add-1 2)
Error: unbound function - ADD-1

Konstrukcja flet tworzy swoje wiązania równolegle, podobnie jak let.

Nie należy jej używać do definiowania lokalnych funkcji rekurencyjnych. Służy do tego konstrukcja labels o takiej samej składni.

Funkcje jako argumenty

Funkcje mogą być argumentami innych funkcji, np.

* (plot #'sin 0 1)
...
* (defun f (x) (+ (* 2 x) (expt x 2)))
F
* (plot #'f -2 3)

Trzeba jednak użyć podobnej do quote konstrukcji function. Powoduje ona potraktowanie jej argumentu jako funkcji zamiast obliczania jego wartości.

* (defun 2+ (x) (+ x 2))
2+
* (defparameter 2+ 3)
3
* 2+
3
* '2+
+
* (function +)
#<FUNCTION 2+>
* #'+                   ;#'2+ to skrót od (function 2+)
#<FUNCTION 2+>

Konstrukcja function przydaje się najczęściej, gdy chcemy przekazać pewną funkcję jako argument do innej funkcji, tak jak w przykładzie powyżej. Zapis #'sin to skrót notacyjny od konstrukcji (function sin), służącej do otrzymania funkcji zdefiniowanej dla symbolu sin.

Funkcje mogą być także wartościami innych funkcji

* (defun która (x y)
    (if (> x y) #'sin #'cos))
KTÓRA
* (która 1 2)
#<FUNCTION COS>
* (funcall (która 1 2) 2)
-0.41614684

Definiowanie funkcji wyższego rzędu

Funkcjami wyższego rzędu nazywamy funkcje, których argumentami lub wynikami mogą być inne funkcje (na przykład funkcja find-if). Przy definiowaniu własnych funkcji wyższego rzędu napotykamy pewien problem. Przypuśćmy, że postanowiliśmy zdefiniowac operację rożniczkowania numerycznego num-deriv, dzialającą jak poniżej:

* (defun f (x) (+ x (expt x 2)))
F
* (num-deriv #'f 1)
3

Niestety definicja

(defun num-deriv (fun x)
  (let ((h 0.00001))
    (/ (- (fun (+ x h))
          (fun (- x h)))
       (* 2 h))))
nie jest poprawna, ponieważ nastąpi próba wywołania funkcji o nazwie fun, a nie funkcji będcej wartością zmiennej fun:
* (num-deriv #'f 1)
error: unbound function - FUN

W takiej sytuacji trzeba skorzystać z funkcji funcall.

(funcall funkcja argument ...)
Argumentami funkcji funcall są funkcja do wywołania i argumenty tego wywołania. Używa się jej najczęściej wtedy, gdy wywoływana funkcja nie jest z góry znana, bo będzie przekazywana jako parametr (np. parametry :test i :key omawiane wcześniej).
* (funcall #'+ 1 2 3)
6
* (funcall #'append '(A B) '(C D) '(E F G))
(A B C D E F G)
* (defparameter lis '(car cadr cdr))
LIS
* (funcall (car lis) '(a b c))
A

Używając funcall możemy zdefiniować num-deriv jako

(defun num-deriv (fun x)
  (let ((h 0.00001))
    (/ (- (funcall fun (+ x h))
          (funcall fun (- x h)))
       (* 2 h))))

Funkcja apply jest podobna do funcall, ale argumenty wywołania przekazuje się jako pojedynczą listę. Używa się jej, gdy liczba argumentów nie jest z góry znana i jest zmienna.

* (apply #'+ '(1 2 3))
6
* (apply #'append '((A B) (C D) (E F G)))
(A B C D E F G)

W wywołaniu apply przed listą argumentów mogą stać dodatkowe wartości, są one do niej dokładane z przodu.

* (apply #'+ 5 '(1 2 3))
11
* (apply #'+ '(5 1 2 3))
11

Używając apply można zdefiniować synonim filter-if dla funkcji remove-if-not (w praktyce używanej częściej niż remove-if):

(defun filter-if (pred sequence &rest specifiers)
  (apply #'remove-if-not pred sequence specifiers))

Jakkolwiek symbole nie należą do typu function}, w pewnych sytuacjach są one automatycznie zamieniane na nazwane nimi funkcje globalne (koercja).

* (funcall 'sin 3)

0.14112
* 

Funkcje anonimowe

Wymyślanie nazw dla zdefiniowania funkcji potrzebnych jednokrotnie jest nużące. Podobny problem dostrzeżono w matematyce, jego rozwiązaniem jest notacja używana przez logików w rachunku lambda, gdzie:

(x) (x + x2)

oznacza ,,funkcja zwracająca x + x2 dla argumentu x.''

Notacji tej używa się również w Lispie do zapisywania funkcji anonimowych

* (lambda (x) (+ x (expt x 2)))
#<FUNCTION (LAMBDA (X)) {1002B4E64B}>

Wyrażenie lambda jest konstrukcją specjalną, której wartością jest funkcja. W starszych wersjach Lispu musiała ona być umieszczana jako argument konstrukcji function, najczęściej przez poprzedzenie jej przez #'

* #'(lambda (x) (+ x (expt x 2)))
#<FUNCTION (LAMBDA (X)) {1002B4E64B}>

Obecnie nie jest to już konieczne, ale tradycyjnie używane

* (num-deriv #'(lambda (x) (+ x (expt x 2)))
             1)
3

Kombinacją lambda i mapcar (lub mapc) można zastąpić wiele pętli iteracyjnych, np. poniższe dwa wyrażenia są równoważne:

* (do ((x '(1 2 3 4 5) (cdr x))
       (y '()))
      ((null x) (nreverse y))
    (push (+ (car x) 2) y))
(3 4 5 6 7)
* (mapcar #'(lambda (x) (+ x 2)) '(1 2 3 4 5))
(3 4 5 6 7)

Wyrażeń lambda można używać do tworzenia i zwracania nowych funkcji jako wartości innych funkcji.

Funkcje wielowartościowe

Funkcja w Lispie może zwracać wiele wartości. Służy do tego konstrukcja

(values wartość1 ... wartośćn)

Interpretery na ogół wypisują te wartości w osobnych liniach

* (values 1 2)
1
2

Aby odebrać te wartości należy użyć odpowiedniej konstrukcji, np. multiple-value-bind.

Konstrukcja multiple-value-bind tworzy nowy blok zmiennych lokalnych jak let, ale inicjuje je kolejnymi zwracanymi wartościami z wywołania wielowartościowego

* (multiple-value-bind (iloraz reszta) (truncate 7 3)
    (list 'iloraz '= iloraz 'reszta '= reszta))
(ILORAZ = 2 RESZTA = 1)

Inne takie konstrukcje to multiple-value-call (wielowartościowe apply), multiple-value-setq i multiple-value-prog1.

Konstrukcja multiple-value-setq

(multiple-value-setq (zmienna ...) wyrażenie)
przypisuje kolejne wartości wyrażenia na podane zmienne. Jeśli jest więcej zmiennych niż wartości, pozostałe otrzymają wartość nil.
* (multiple-value-setq (quotient remainder)
    (floor 14 -4))
-4

* quotient
-4

* remainder
-2

W wielu kontekstach, na przykład gdy wywołanie wielowartościowe jest argumentem wywołania jakiejś zwykłej funkcji, wzięta zostanie tylko pierwsza wartość, a reszta będzie zignorowana.

Konstrukcja multiple-value-prog1 to wielowartościowy odpowiednik prog1

(multiple-value-prog1 wyrażenie-1 wyrażenie ...)
Oblicza się po kolei wszystkie wyrażenia, po czym zwraca wartości pierwszego z nich.
* (setq num 10)
10

* (multiple-value-prog1
      (floor num 3)
    (incf num))
3
1

* num
11

Jeśli chcielibyśmy wybrać inną ze zwracanych wartości (na przykład drugą), to możemy użyć makra nth-value. Jeśli funkcja rozmiar-okna zwraca szerokość i wysokość okna, a zależy nam tylko na wysokości

  (nth-value 1 (rozmiar-okna okno))
Pierwszy argument to numer wartości (jak zwykle liczymy od zera).

Konstrukcja

(multiple-value-call funkcja wyrażenie ...)
Oblicza kolejno podane wyrażenia, po czym wywołuje funkcję od wszystkich wartości zwróconych przez te wyrażenia. Zwraca wyniki wywołania tej funkcji.

Przypomina to apply albo funcall. Przykłady:

* (multiple-value-call #'list
      (floor 4.5) (floor 5.5))
(4 0.5 5 0.5)

* (multiple-value-call #'list
      (floor 4.5) 'foo)
(4 0.5 foo)

Można też automatycznie zebrać zwracane wartości w listę konstrukcją multiple-value-list

(multiple-value-list wyrażenie)

Przykłady

* (multiple-value-list (truncate 1.2))
(1 .2)

* (multiple-value-list (+ 1 2))
(3)

* (multiple-value-list (values))
NIL

Można użyć (values) jeśli się chce, żeby procedura nic nie zwracała. Warto tak robić, jeśli funkcja nie zwraca żadnej użytecznej wartości (tak jak funkcje typu void w języku C).

Do czytelnego rozpakowania struktury listowej na zmienne lokalne można użyć konstrukcji destructuring-bind zbliżonej do let, ale używającej wzorca o składni parametrów procedur. Symbolom we wzorcu odpowiadają nazwy zmiennych lokalnych

* (destructuring-bind ((v1 v2) &key a b) '((1 2) :b 3 :a 4)
    (list a b v1 v2)
(4 3 1 2)

Czasem może przydać się funkcja

(values-list lista)

Zwraca ona elementy podanej listy jako osobne wartości.

Uwaga: w niektórych miejscach zwraca się tylko jedną wartość z values. Tak jest na przykład w warunkach cond, popatrzmy

* (cond ((values 1 2)))

1
* (cond (t (values 1 2)))

1
2