common-lisp
Functies als eerste klas waarden
Zoeken…
Syntaxis
- (functienaam); haalt het functieobject van die naam op
- #'naam ; syntactische suiker voor (functienaam)
- (symbool-functiesymbool); retourneert de functie gebonden aan symbool
- (funcall-functie args ...); oproepfunctie met args
- (pas functie arglist toe); aanroepfunctie met argumenten in een lijst
- (pas functie arg1 arg2 ... argn arglist toe); call-functie met argumenten gegeven door arg1, arg2, ..., argn en de rest in de lijst arglist
parameters
Parameter | Details |
---|---|
naam | een (niet geëvalueerd) symbool dat een functie een naam geeft |
symbool | een symbool |
functie | een functie die moet worden opgeroepen |
args ... | nul of meer argumenten ( geen lijst met argumenten) |
arglist | een lijst met argumenten die aan een functie moeten worden doorgegeven |
arg1, arg2, ..., argn | elk is een enkel argument dat aan een functie moet worden doorgegeven |
Opmerkingen
Wanneer we het hebben over Lisp-achtige talen is er een gemeenschappelijk onderscheid tussen wat bekend staat als een Lisp-1 en een Lisp-2. In een Lisp-1 hebben symbolen alleen een waarde en als een symbool naar een functie verwijst, is de waarde van dat symbool die functie. In een Lisp-2 kunnen symbolen afzonderlijke bijbehorende waarden en functies hebben en daarom is een speciale vorm vereist om te verwijzen naar de functie die is opgeslagen in een symbool in plaats van de waarde.
Common Lisp is in feite een Lisp-2, maar er zijn in feite meer dan 2 naamruimten (dingen waarnaar symbolen kunnen verwijzen) - symbolen kunnen bijvoorbeeld verwijzen naar waarden, functies, typen en tags.
Anonieme functies definiëren
Functies in Common Lisp zijn eerste klas waarden . Een anonieme functie kan worden gemaakt met behulp van lambda
. Hier is bijvoorbeeld een functie van 3 argumenten die we vervolgens met behulp van funcall
noemen
CL-USER> (lambda (a b c) (+ a (* b c)))
#<FUNCTION (LAMBDA (A B C)) {10034F484B}>
CL-USER> (defvar *foo* (lambda (a b c) (+ a (* b c))))
*FOO*
CL-USER> (funcall *foo* 1 2 3)
7
Anonieme functies kunnen ook direct worden gebruikt. Common Lisp biedt hiervoor een syntaxis.
((lambda (a b c) (+ a (* b c))) ; the lambda expression as the first
; element in a form
1 2 3) ; followed by the arguments
Anonieme functies kunnen ook worden opgeslagen als globale functies:
(let ((a-function (lambda (a b c) (+ a (* b c))))) ; our anonymous function
(setf (symbol-function 'some-function) a-function)) ; storing it
(some-function 1 2 3) ; calling it with the name
Geciteerde lambda-expressies zijn geen functies
Merk op dat lambda-expressies tussen aanhalingstekens geen functies zijn in Common Lisp. Dit werkt niet :
(funcall '(lambda (x) x)
42)
Gebruik coerce
, eval
of funcall
om een aangehaalde lambda-expressie om te zetten in een functie:
CL-USER > (coerce '(lambda (x) x) 'function)
#<anonymous interpreted function 4060000A7C>
CL-USER > (eval '(lambda (x) x))
#<anonymous interpreted function 4060000B9C>
CL-USER > (compile nil '(lambda (x) x))
#<Function 17 4060000CCC>
Verwijzend naar bestaande functies
Elk symbool in Common Lisp heeft een slot voor een te binden variabele en een afzonderlijke slot voor een te binden functie.
Merk op dat de naamgeving in dit voorbeeld alleen ter illustratie is. Globale variabelen moeten niet foo
worden genoemd, maar *foo*
. De laatste notatie is een conventie om duidelijk te maken dat de variabele een speciale variabele is die dynamische binding gebruikt .
CL-USER> (boundp 'foo) ;is FOO defined as a variable?
NIL
CL-USER> (defvar foo 7)
FOO
CL-USER> (boundp 'foo)
T
CL-USER> foo
7
CL-USER> (symbol-value 'foo)
7
CL-USER> (fboundp 'foo) ;is FOO defined as a function?
NIL
CL-USER> (defun foo (x y) (+ (* x x) (* y y)))
FOO
CL-USER> (fboundp 'foo)
T
CL-USER> foo
7
CL-USER> (symbol-function 'foo)
#<FUNCTION FOO>
CL-USER> (function foo)
#<FUNCTION FOO>
CL-USER> (equalp (quote #'foo) (quote (function foo)))
T
CL-USER> (eq (symbol-function 'foo) #'foo)
T
CL-USER> (foo 4 3)
25
CL-USER> (funcall foo 4 3)
;get an error: 7 is not a function
CL-USER> (funcall #'foo 4 3)
25
CL-USER> (defvar bar #'foo)
BAR
CL-USER> bar
#<FUNCTION FOO>
CL-USER> (funcall bar 4 3)
25
CL-USER> #'+
#<FUNCTION +>
CL-USER> (funcall #'+ 2 3)
5
Hogere orderfuncties
Common Lisp bevat veel hogere orde functies die functies voor argumenten worden doorgegeven en ze aanroepen. Misschien wel de meest fundamentele zijn funcall
en zijn van apply
:
CL-USER> (list 1 2 3)
(1 2 3)
CL-USER> (funcall #'list 1 2 3)
(1 2 3)
CL-USER> (funcall #'list 1 2 3 4 5)
(1 2 3 4 5)
CL-USER> (apply #'list '(1 2 3))
(1 2 3)
CL-USER> (apply #'list 1 2 '(4 5))
(1 2 3 4 5)
CL-USER> (apply #'+ 1 (list 2 3))
6
CL-USER> (defun my-funcall (function &rest args)
(apply function args))
MY-FUNCALL
CL-USER> (my-funcall #'list 1 2 3)
(1 2 3)
Er zijn veel andere hogere-orde-functies die bijvoorbeeld een functie vaak toepassen op elementen van een lijst.
CL-USER> (map 'list #'/ '(1 2 3 4))
(1 1/2 1/3 1/4)
CL-USER> (map 'vector #'+ '(1 2 3 4 5) #(5 4 3 2 10))
#(6 6 6 6 15)
CL-USER> (reduce #'+ '(1 2 3 4 5))
15
CL-USER> (remove-if #'evenp '(1 2 3 4 5))
(1 3 5)
Een lijst samenvatten
De verkleiningsfunctie kan worden gebruikt om de elementen in een lijst op te tellen.
(reduce '+ '(1 2 3 4))
;;=> 10
Standaard verlagen voert een linker-associatief verminderen, wat betekent dat de som 10 wordt berekend als
(+ (+ (+ 1 2) 3) 4)
De eerste twee elementen worden eerst opgeteld en vervolgens wordt dat resultaat (3) toegevoegd aan het volgende element (3) om 6 te produceren, dat op zijn beurt wordt opgeteld bij 4, om het eindresultaat te produceren.
Dit is veiliger dan het gebruik van toepassing zijn (bijvoorbeeld in (van toepassing '+' (1 2 3 4)), omdat de lengte van de lijst met argumenten die kunnen worden doorgegeven aan toe te passen is beperkt (zie call-argumenten-limiet) en het verminderen van de wil werk met functies waarvoor slechts twee argumenten nodig zijn.
Door het from-end trefwoordargument op te geven, zal reduceren de lijst in de andere richting verwerken, wat betekent dat de som in de omgekeerde volgorde wordt berekend. Dat is
(reduce '+ (1 2 3 4) :from-end t)
;;=> 10
is aan het rekenen
(+ 1 (+ 2 (+ 3 4)))
Implementeren van reverse en revappend
Common Lisp heeft al een omgekeerde functie, maar als dit niet het geval was, kon het eenvoudig worden geïmplementeerd met behulp van verkleinen . Gegeven een lijst als
(1 2 3) === (cons 1 (cons 2 (cons 3 '())))
de omgekeerde lijst is
(cons 3 (cons 2 (cons 1 '()))) === (3 2 1)
Dat is misschien geen voor de hand liggend gebruik van reductie , maar als we een functie "omgekeerde nadelen" hebben, zeg xcons , zodanig dat
(xcons 1 2) === (2 . 1)
Vervolgens
(xcons (xcons (xcons () 1) 2) 3)
wat een reductie is.
(reduce (lambda (x y)
(cons y x))
'(1 2 3 4)
:initial-value '())
;=> (4 3 2 1)
Common Lisp heeft nog een nuttige functie, revappend , die een combinatie is van reverse en append . Conceptueel keert het een lijst om en voegt het toe aan een staart:
(revappend '(3 2 1) '(4 5 6))
;;=> (1 2 3 4 5 6)
Dit kan ook worden geïmplementeerd met verkleinen . Het is feitelijk hetzelfde als de implementatie van reverse hierboven, behalve dat de beginwaarde (4 5 6) moet zijn in plaats van de lege lijst.
(reduce (lambda (x y)
(cons y x))
'(3 2 1)
:initial-value '(4 5 6))
;=> (1 2 3 4 5 6)
sluitingen
Functies onthouden de lexicale reikwijdte waarin ze werden gedefinieerd. Daarom kunnen we een lambda insluiten in een let om sluitingen te definiëren.
(defvar *counter* (let ((count 0))
(lambda () (incf count))))
(funcall *counter*) ;; => 1
(funcall *counter*) ;; = 2
In het bovenstaande voorbeeld is de tellervariabele alleen toegankelijk voor de anonieme functie. Dit is duidelijker te zien in het volgende voorbeeld
(defvar *counter-1* (make-counter))
(defvar *counter-2* (make-counter))
(funcall *counter-1*) ;; => 1
(funcall *counter-1*) ;; => 2
(funcall *counter-2*) ;; => 1
(funcall *counter-1*) ;; => 3
Functies definiëren die functies aannemen en teruggeven
Een eenvoudig voorbeeld:
CL-USER> (defun make-apply-twice (fun)
"return a new function that applies twice the function`fun' to its argument"
(lambda (x)
(funcall fun (funcall fun x))))
MAKE-APPLY-TWICE
CL-USER> (funcall (make-apply-twice #'1+) 3)
5
CL-USER> (let ((pow4 (make-apply-twice (lambda (x) (* x x)))))
(funcall pow4 3))
81
Het klassieke voorbeeld van functiesamenstelling : ( f ∘ g ∘ h ) ( x ) = f ( g ( h ( x )):
CL-USER> (defun compose (&rest funs)
"return a new function obtained by the functional compositions of the parameters"
(if (null funs)
#'identity
(let ((rest-funs (apply #'compose (rest funs))))
(lambda (x) (funcall (first funs) (funcall rest-funs x))))))
COMPOSE
CL-USER> (defun square (x) (* x x))
SQUARE
CL-USER> (funcall (compose #'square #'1+ #'square) 3)
100 ;; => equivalent to (square (1+ (square 3)))