© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Funktioner

Opgaver

 

 

Erklæring er et kald Vi har allerede i introduktion set den syntaktiske opbygning af et kald, men hvordan erklærer man en funktion? Det gøres naturligvis også ved at kalde en funktion!
 
> (defun sum (x y) (+ x y))
SUM
> (sum 7 3)
10
defun Efter vi har erklæret funktionen: sum, findes den i systemet og vi kan efterfølgende bruge den. Det syntaktiske opbygning er noget for sig. Ser man på den "ydre liste" består den af fire ting. Først er der navnet på funktionen: defun, som vi bruger til at erklære funktioner. Dernæst er der navnet på den funktion vi ønsker at erklære. Som det tredie er der en liste med navnene på de formelle parametre. Og fjerde og sidst har vi, hvad vi kunne kalde funktions-kroppen, i dette tilfælde et kald af en anden funktion.
  Funktions-kroppen i Lisp er lidt speciel. Ovenfor har vi et enkelt kald af funktionen: +, men vi kunne have en række kald - en funktions-krop i List er en sekvens af kald. Lad os se et banalt eksempel, der illustrere dette:
 
> (defun sum (x y) (+ x y) (- x y))
SUM
> (sum 7 3)
4
  Nu er navnet: sum, ikke længere passende, og funktionen er i det hele taget ikke særlig hensigtsmæssig. Det er dog tydeligt at funktionen returnerer 7-3 = 4, og at det dermed er resulatet af det sidste kald i kroppen der returneres.
  Man bemærker, at erklæringen af en funktion - kaldet af: defun - returnerer den erklærede funktion.
 

 

1. Højere-ordens funktioner

Funktion som parameter En højereordens funktion, er en funktion der kan tage en eller flere funktioner som parametre, og kan kalde dem. Lad os se et enkelt eksempel:
 
> (defun g (x y) (+ x y))
G
> (defun f (h x y) (funcall h  x y))
F
> (f (quote g) 6 5)
11
  Her erklærer vi to funktioner: g og f; hvorefter vi kalder f med funktionen h som den første parameter. Man bemærker at g ikke anføres direkte, såfremt man gjorde dette:
 
> (f g 6 5)

*** - EVAL: variable G has no value
1. Break>
Evaluere symbolet g blev man prompte smidt i debug-mode. Det skyldes at Lisp ved kaldet af f vil evaluere g, for at finde ud af hvilken aktuel parameter den skal sende med. Den begynder derfor at lede efter et symbol g med en værdi og finder det ikke - for g er en funktion!
quote Funktionen: quote anvendes når noget skal tages som det er - at det ikke skal evalueres. Ved at kalde quoteg, vil g ikke blive evalueret, men i sig selv blive den aktuelle parameter.
  Man anvender ikke så sjældent quote, og der er derfor indført et stykke syntaktisk sukker for at det er lettere at leve med :-)
 
> (f 'g 6 5)
11
' Man sætter ganske enkelt en apostrof foran et symbol der ikke skal evaluere.

 

2. Parametre

Vi har flere gange brugt parametre ovenfor, men vi vil her se på nogle specielle muligheder, der findes i Lisp.

 

2.1 Variabelt antal parametre

Vi har allerede set funktioner, der tager et variabelt antal parametre, f.eks.: list-funktionen, men hvordan laver man sådanne funktioner?
  Når man vil lave en funktion, der kan tage et variabelt antal parametre, skal man først fastlægge, hvor mange af parametrene der skal være anført i kaldet. Det kunne være nul, men lad os f.eks. lave en funktion: f, der altid skal kaldes med mindst én parameter:
 
> (defun f (x &rest y) y)
F
> (f 1 2 3 4 5)
(2 3 4 5)
Det gøres ved at placere: &rest før den sidste parameter. Alle parametre der står før &rest skal anføres i kaldet, mens evt. yderligere parametre vil blive placeret i en liste, der overføres til den parameter, der er anført efter &rest - i dette tilfælde parameteren y.
Vi ser i eksemplet ovenfor, at de ekstra parametre: 2 3 4 5, placeres i en liste.

 

2.2 Valgfrie parametre

Man kan også gå den anden vej, og gøre parametre valgfrie. Lad os se et eksempel:
 
> (defun f (x &optional y) y)
F
> (f 1 2)
2
> (f 1)
NIL
Her er &optional placeret før parameteren y, og alle parametre efter &optional kan udelades i kald af funktionen f. Når man udelader en eller flere parametre i et kald, vil de valgfrie parametre få tildelt værdien nil.

 

2.2.1 Default parametre

Ønsker man at valgfrie parametre i stedet skal tildeles en anden værdi en nil; hvis de udelades i et kald af funktionen, kan man anføre en alternativ default-værdi. Dette gøres ved at samlede den formelle parameter og dens default-værdi i paranteser. Lad os se et eksempel:
 
> (defun f (&optional (x 1) (y 2)) (cons x y))
F
> (f 3 4)
(3 . 4)
> (f 3)
(3 . 2)
> (f)
(1 . 2)
Her har vi to valgfrie parametre, der begge har default-værdier.

 

2.3 Keyword parametre

Det er muligt at anvende parametres formelle navne i et kald af funktionen. Lad os se et eksmepel:
 
> (defun f (&key x y) (cons x y))
F
> (f :x 1 :y 2)
(1 . 2)
> (f :y 1 :x 2)
(2 . 1)
Her erklærer vi to parametre x og y, og ved at sætte &key foran, kan disse parametres navne nu anvendes i kaldet. Vi ser hvordan det f.eks. bliver muligt at bytte om på rækkefølgen, blot vi knytter den ønskede værdi til det rigtige navn. Det skal bemærkes at parametres navne nu skal anvendes, man kan ikke længere blot kalde:
Dur ikke
(f 1 2)
Keyword parametre er dog også valgfrie parametre, så det er muligt helt at udelade dem.

 

2.3.1 Både keyword og default parameter

Man kan samle det hele ved at give keyword parametre en default-værdi. Lad os se et eksempel:
 
> (defun f (&key (x 1) (y 2)) (cons x y))
F
> (f :x 3 :y 4)
(3 . 4)
> (f :y 4)
(1 . 4)
> (f)
(1 . 2)
Her skal man anvende parametrenes navne; hvis de anføres, men udelader man dem får de den default-værdi der er anføret i erklæringen af funktionen.

 

3. Navnløse funktioner

De funktioner vi hidtil har set, har alle haft et navn; hvilket gør det muligt at kalde dem. Det er dog ikke altid man har brug for et sådant navn, specielt når der er tale om en midlertidig funktion. En sådan funktion erklæres med lambda, f.eks.:
 
> (defun double-func (f x) (* 2 (funcall f x)))
DOUBLE-FUNC
> (double-func (lambda (x) (+ x 5)) 4)
18
Her erklærer vi først en funktion, der tager en anden funktion som parameter. Dernæst foretager vi et kald, men da vi ikke er interesseret i den funktion vi sender med som parameter, andet end som parameter, erklærer vi en midlertidig navnløs funktion i selve kaldet.

 

4. Kontrolstrukturer

 

4.1 Selektion

Den mest elementære kontrolstruktur er if-sætningen, eller if-kaldet - for alt er jo funktioner i Lisp. F.eks.:
 
> (if (> 5 4) 1 0)
1
> (if (< 5 4) 1 0)
0
> (if t 1 0)
1
> (if nil 1 0)
0
Funktionen: if, tager tre parametre. Den første er en boolsk værdi - her et kald af funktionerne: > og <, der sammenligner to værdier. Dernæst er der det, der skal returneres; hvis det er sandt og som tredie parameter, det der skal returneres; hvis det er falsk.
nil betyder falsk og t betyder sand (eng.: true). Man bemærker at nil dermed kan have flere betydninger. Da nil betyder falsk, er en ikke-tom liste tilsvarende det samme som sand:
 
> (if (list 1 2 3) 1 0)
1
Vil man i en if-sætning ikke have en "enten eller"-opdeling, fordi en af delene mangler, kan man anvende en af funktionerne: when og unless:
 
> (when (< 3 4) 5)
5
> (unless (< 3 4) 5)
NIL
when-funktionen svarer til en if, uden nogen else, og unless-funktionen svarer til en if-else uden en if - dvs kun else-delen.
Lisp har en case-funktion i stil med den switch-sætning vi kender fra Java. F.eks.:
 
> (defun er-primtal (x)
    (case x
      ((1 4 6 8 9 10)
        nil
      )
      ((2 3 5 7)
        t
      )
    )
  )
ER-PRIMTAL
> (er-primtal 5)
T

 

4.2 Iteration

 

4.2.1 loop

loop-løkken er den simpleste løkke man kan lave i Lisp. Lad os se et eksempel:
 
> (setq x 10)
10
> (setq y 0)
0
> (loop
    (setq x (- x 1))
    (setq y (+ y 2))
    (when (< x 1)
      (return y)
    )
  )
20
Det hele kunne naturligvis placeres i en funktion, men vi har undladt dette for at kunne koncentrere os om selve loop-løkken.
loop-løkkens krop bliver udført, indtil der udføres et return. I forbindelse med et return, returneres fra loop-løkken, med den værdi der er angivet efter return - syntaktisk meget lig det vi kender fra Java.

 

4.2.2 dolist

En dolist-løkke er også meget simpel. Den giver mulighed for at gennemløbe elementerne i en liste og gøre noget for hver af dem. Lad os se et eksempel:
 
> (dolist (x (list 3 6 9 12 15)) (print x))

3
6
9
12
15
NIL
Man skal bemærke at en dolist altid returnerer nil - det er ikke x der slutter med at være nil.
[Jeg ved ikke hvorfor der altid kommer en tom linie først]

 

4.2.3 do

do-løkken er mere kompliceret end loop-løkken, men som vi skal se, ligner den meget for-løkken som vi kender fra Java. Lad os se et eksempel:
 
> (setq sum 0)
0
> (do
    ((x 1 (+ x 1)))
    ((> x 10) sum)
      (setq sum (+ sum x))
  )
55
Lad os tage denne struktur i flere brudstykker:
 
Først er der tælleren. En do-løkke kan have et vilkårligt antal tællere. I eksemplet ovenfor har vi dog kun én: x:
(()()..())
((x 1 (+ x 1)))
3-tuple Vi har med blåt markeret det der vedrører x, mens den røde parantes udenom indeslutter de tællere der måtte være. Der er tre ting i hver af disse tæller-specifikationer: tælleren, dens initielle værdi og det der skal gøres for hver iteration - dvs. dens opdatering hver gang vi har kørt rundt i løkken. I vores eksempel hedder tælleren: x, og dens initielle værdi er 1. Hver gang løkken har kørt en gang rundt skal x tælles op med 1.
 
Det næste vi finder i do-løkken er terminerings-beskrivelsen:
((> x 10) sum)
2-tuple Først står terminerings-betingelsen, der beskriver hvornår do-løkken skal terminere. Dernæst står, hvad der skal returneres fra do-løkken - i vores eksempel, er det den værdi, der er tilknyttet sum.
 
Det sidste vi finder i do-løkken er dens krop, det er det som bliver udført for hver iteration.