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
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
.
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!
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 *
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 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
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 *
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:
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.
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