Anders Haraldsson 1 Anders Haraldsson 2 Dagens föreläsning Programmering i Lisp Fö 5 - Funktioner - lambda-uttryck (avs 7.1) - funcall och function (avs 7.2) - Högre ordningens funktioner (avs 7.) - Iteratorer - Egenskaper hos funktioner / operatorer - Sammanfattning av rekursiva strukturer Funktion - Lambda-uttryck Vid definitionen (defun square (x) (* x x)) definieras funktionen: (lambda (x) (* x x)) namn på denna funktion är: square Ett uttryck (form) i LISP är: (funktion argument... argument) funktion beskrivs av ett namn eller ett lambda-uttryck. Exempel som lambda-uttryck: ((lambda (x) (* x x)) 10) => 100 Anders Haraldsson Anders Haraldsson 4 Första ordningens dataobjekt En funktion i Lisp (och i de funktionella programspråken) är första ordningens dataobjekt i språket, dvs det är även data på samma sätt som andra datatyper (heltal, symboler, listor etc). Vi kan skriva funktioner som i sin tur tar funktioner som argument, returnerar en funktion som argument. Dessa funktioner brukar kallas högre ordningens funktioner. Vi kan via lambda-uttryck skapa godtyckliga funktioner under exekvering. Detta kräver tillgång till Lisp-interpretatorn (eval, apply/funcall). Vi kan ha dataobjekt med funktioner, t ex en associationslista med funktioner. ( (1. funktionen first ) (2. funktionen second ) (. funktionen third )... (20. funktionen som tar ut 20:e elementet på en lista)) Exempel: Skriv en funktion som summerar Σf(i), dvs f(1) + f(2) + f() i=1 Det som är okänt är vilken funktion som skall användas. Formella parametern f i summa-funktionen binds till den funktion, som skall användas för summeringen. (defun sum (f) (+ (funcall f 1) (funcall f 2) (funcall f ))) Funcall är en funktion som är en del av Lisp-interpretatorn, som tar ett godtyckligt antal argument (som beräknas i vanlig ordning). Första argumentets värde skall vara en funktion, och övriga arguments värden blir argumenten till denna funktion. Σsin(i) i=1 (sum (function sin)) Σi 2 + (sum (function i=1 (lambda (i) (+ (* i i) ))))
Anders Haraldsson 5 Anders Haraldsson 6 Function och # (function sin) (function (lambda (x) (* x x)) Har i stort samma funktion som quote, dvs skyddar sitt argument från vidare beräkning. I många fall skulle quote kunna användas, men det är praxis och i vissa fall nödvändigt att använda function. Ett förkortat skrivsätt för function är # # sin -> (function sin) # (lambda (x) (* x x)) -> (function (lambda (x) (* x x))) Jämför kalle -> (quote kalle) (lisa karin) -> (quote (lisa karin)) Detta är sk read-macron som direkt vid inmatning gör översättningen. Exempel på en funktion som returnerar en annan funktion: (defun choose-function (i) ((= i 1) (function 1+)) ((= i 2) (function (lambda (n) (+ n 2)))) (t (function (lambda (n) (+ n 10)))))) (funcall (choose-function 2) 5) => 7 (setq my-fn (choose-function )) (funcall my-fn 2) => 12 (my-fn 2) =>? Mer om detta att skapa funktioner beskrivs i kapitel 18 Lexical closure, där function beskrivs Anders Haraldsson 7 Anders Haraldsson 8 Repetition och iteratorer Vi har använt rekursion för att göra om något repetitivt (många olika mallar). Den andra möjligheten vi nu inför är med högre ordningens funktioner. Gör något med varje element på en lista. Gör något med varje löv i ett binärt träd. Gör något med varje nyckeln i en associationslista. Gör något med namnet i varje post i en databas. Många språk börjar införa denna typ av konstruktion. Där kallas de ofta iteratorer. Det är en generell funktion, som man kan styra genom, i Lisp-fallet ge funktioner som argument som anger vad som skall göras. Det vi hitills inte infört är den explicita repetitionssatsen, t ex for, while, loop Mallar som högre ordningens funktioner: Skriv en funktion som ökar varje element på en lista med 5. (defun öka-5 (l) (+ 5 (first l)) (öka-5 (rest l))) ))) (öka-5 (1 10 50)) => (6 15 55) Skriv en funktion som skapar en ny lista av alla förstaelementen på en lista av dellistor. (defun första-element (l) (first (first l)) (första-element (rest l))) ))) (första-element ((one ett) (two två) (three tre))) => (one two three)
Anders Haraldsson 9 Anders Haraldsson 10 Vi tänker oss en mall för genomgång av elementen i en lista och som resultat erhålla en ny lista där en funktion har applicerats på varje element (defun fn-mall (l) ( operation (first l)) (fn-mall (rest l))))))) Vi visar här hur en högre ordningens funktion mapcar kan definieras: (defun my-mapcar (fn l) (funcall fn (first l)) (my-mapcar fn (rest l))) ))) fn = operation eller "funktion Map-funktioner Common Lisp innehåller ett flertal sådana funktioner. Flera börjar på map, och kallas map-funktioner. (mapcar #'square '(1 2 4)) => (1 2 2 2 2 4 2 ) = (1 4 9 16) (mapcar #'(lambda (n) (+ n 10)) '(1 2 4)) => ( 1+10 2+10 +10 4+10 ) = (11 12 1 14) (mapcar #'(lambda (n) (if (< n 0) (- n) n)) '(1-2 -4)) => (1 2 4) Anders Haraldsson 11 Anders Haraldsson 12 Vi kan nu definiera de tidigare definierade funktionerna med mapcar. (defun öka-5 (l) (mapcar # (lambda (n) (+ n 5)) l)) (defun första-element (l) (mapcar # first l)) Map-funktioner i Common Lisp (mapcar # first ((a b c) (x y z))) => (a x) Kan användas med flera argument: (mapcar # (lambda (x y z) (+ x y z)) (1 2 ) (10 20 0) (100 200 00)) => (111 222 ) Vi använder en generell komponent (mapcar), som specialiceras för ett givet ändamål. Funktion utförs för sin sidoeffekts skulle, inget intressant värde returneras: (mapc # print (anna kalle stina)) anna kalle stina
Anders Haraldsson 1 Anders Haraldsson 14 Applicerar en funktion på successiva svansar: (maplist # length (a b c d)) => (4 2 1) Finns den rekursiva processlösningsmallen som en högre ordningens funktion? Ja, funktionen reduce kan användas för listor. Villkorsfunktioner: gäller för alla?, gäller för någon?,... (some # numberp (a 1 b c)) => t (every # numberp (1 2 4)) => t (notany # numberp (a b c d)) => t (notevery # numberp (a b c 1 d)) => t Vissa vanliga funktioner finns i if och if-notvarianter: (member-if # numberp (a b 1 c d)) => t (remove-if # numberp (a b 1 c d)) => (a b c d) (remove-if-not # numberp (a b 1 c d)) => (1) (reduce fn lista :initial-value init :from-end t) om lista är (e 1 e 2 e.. e n-1 e n ) motsvaras detta av en högerassociativ reducering av listan med fn som operator. (fn e 1 (fn e 2 (fn e... (fn e n init)..))) Med (reduce fn lista :initial-value init) erhålls en vänsterassociativ reducering (fn (fn... (fn (fn e 1 e 2 ) e ).. e n-1 ) e n ) Anders Haraldsson 15 Anders Haraldsson 16 Hur beräknas? 1-2 - - 4 (((1-2) - ) - 4) =? (1 - (2 - ( - 4))) =? 4 2 (2 ) 4 =? ( 4 ) 2 =? (expt (expt 2 ) 4) = 4096 (expt 2 (expt 4)) = 2417851692292584941252 (expt 2 4) ger fel (måste vara två argument) Rekursiva processlösningsmallen: Vi beräknar: 1 + 2 + + 4 (((1 + 2) + ) + 4) (1 + (2 + ( + 4))) Högerassociativ (defun addera-h (l) 0 (+ (first l) (addera-h (rest l))))) (addera-h (1 2 4)) -> (+ 1 (+ 2 (+ (+ 4 0))) ; (1 + (2 + ( + 4))) (defun addera-h (l) (reduce # + l :initial-value 0 :from-end t)) Vänsterassociativ (defun addera-v (l) (if (endp (rest l)) (first l) (+ (addera-v (butlast l)) (first (last l))))) (defun addera-v (l) (reduce # + l :initial-value 0 :from-end nil)) (addera-v (1 2 4)) -> (+ (+ (+ 1 2) ) 4) ; (((1 + 2) + ) + 4)
Anders Haraldsson 17 Anders Haraldsson 18 Viktiga egenskaper hos operatorer eller funktioner kommutativ operator x op y y op x Operatorer: + * snitt union +4 är samma som 4+ union av mängder: {a b c} U {a c d e} = {a b c d e} är samma som {a c d e} U {a b c} = {a b c d e} associativ operator (x op y) op z x op (y op z) Kan därför oftast skrivas x op y op z (1 + 2) + är samma som 1 + (2 + ), som är 1+2+ högerassociativ operator x op y op z tolkas som (x op (y op z)) vänsterassociativ operator x op y op z tolkas som ((x op y) op z) Vilka krav har vi på operatorn/funktionen om vi kan använda samma vid rekursiv och iterativ processlösning. Rekursiv processlösning (defun f (l) värde (operator/funktion (first l) (f (rest l))))) Iterativ processlösning (defun f (l) (f-iter l värde )) (defun f-iter (l res) res (f-iter (rest l) (operator/funktion (first l) res)))) operatorn/funktionen skall vara kommutativ och associativ Anders Haraldsson 19 Anders Haraldsson 20 Rekursiv processlösning (defun sum (l) 0 (+ (first l) (f (rest l))))) (sum (1 2 4)) -> (+ 1 (+ 2 (+ (+ 4 0)))) 1 + 2 + + 4 Iterativ processlösning (defun sum (l) (f-iter l 0)) (defun f-iter (l res) res (f-iter (rest l) (+ (first l) res)))) (sum (1 2 4)) -> (+ 4 (+ (+ 2 (+ 1 0)))) Går det med -? Koll av egenskaper för funktionerna append och union Rekursiv processlösning (defun sätt-ihop (l) ; l är en lista med listor () (append (first l) (sätt-ihop (rest l))))) (sätt-ihop ((1 2) ( 4) (5))) -> (append (1 2) (append ( 4) (append (5) ()))) => (1 2 4 5) Iterativ processlösning (defun sätt-ihop (l) (sätt-ihop-iter l ())) (defun sätt-ihop-iter (l res) res (sätt-ihop-iter (rest l) (append (first l) res)))) (sätt-ihop ((1 2) ( 4) (5))) -> (append (5) (append ( 4) (append (1 2) ()))) => (5 4 1 2) Med union så blir det samma resultat, som mängd. Ordningen mellan elementen i en mängd har ingen betydelse.
Anders Haraldsson 21 Anders Haraldsson 22 Koll av egenskaper för funktionerna append och union append är ej kommutativ (append x y) är ej lika med (append y x) append är associativ (append x (append y z)) lika med (append (append x y) z) därför kan vi tillåta append ta godyckligt antal argument och inte behöva veta i vilken ordning de sätts samman. (append (1 2) ( 4) (5)) => (1 2 4 5) union är kommutativ och associativ (union x y) är lika med (union y x) Sammanfattning av rekursiva modeller. Vi har två diskretmatematiska begrepp sekvens, elementen följer efter varandra s 1 s 2 s... s 4 exempel: en sekvens med element i en lista (lisa kalle svea tore) en följd med tecken för att bilda ett ord kalle en följde med ord för att bilda en mening idag är det vacker väder binärt träd, elementen delas upp i två hälfter + * / 2 4-8 2 exempel: en lista sedd som punkterade par ett aritmetiskt uttryck, där en nod innehåller en operator Anders Haraldsson 2 Anders Haraldsson 24 Bearbetning av elementen i dessa två strukturer följer olika rekursiva mallar: Om vi definierar en funktion f som skall bearbeta en sekvens så har vi fallen: 1) vad skall vi göra för tomma sekvensen? 2) vad skall vi göra för operation på första elementet och det värde vi får då vi utför f på resten av sekvensen Om ett element i sekvensen i sin tur innehåller en sekvens får vi en mall med tre fall. Om vi definierar en funktion f som skall bearbeta ett binärt träd så har vi fallen: 1) vad skall vi göra för lövet? 2) vad skall vi göra för operation på det värde vi får från att utföra f på vänster delträd och det värde vi får från att utföra f på höger delträd Detta är generella algoritmer och används på många olika slags datasstrukturer. I denna kurs använder vi den först på vanliga Lisp-listor. Vi kan se listan som en representation av antingen en sekvens eller ett binärt träd. Sedan använder vi binära träd för att kunna representera och bearbeta formeluttryck. Vi iillustrerar listorna grafiskt med cons-celler och pekare. Listan sedd som en sekvens (utvidgat till att elementen kan vara en sekvens). Listor i listor. Dubbelrekursion. mall: (defun fn (l) ((endp l) init-värde ) ((atom (first l)) ( operation 1 (first l) (fn (rest l)))) (t ( operation 2 (fn (first l)) (fn (rest l)))) )) Listan sedd som binärt träd. I Lisp punkterade par. b a a c b nil d mall: (defun fn (bt) (if (atom bt) bearbetning lövet ( operation (fn (car bt)) (fn (cdr bt))))) c nil d (a (b c) d) (a. ((b. (c. nil)). (d. nil))
Anders Haraldsson 25 - Vi har lärt oss skriva algoritmer som bearbetar sekvenser och binär träd-strukturer. - Vi har identifierat olika typer av mallar eller mönster. - Vi kan traversera en sådan struktur, dvs gå igenom den element för element. Vi kan nu identifiera olika typer av algoritmmönster: sökning efter givet element (finns-bokstav? i Övn 118, forwardfind i Uppg 2B) mappning, transformera varje elements värde till ett nytt och skapa en struktur av samma slag. (positiva i Uppg 1A, högre ordningens funktion mapcar) filtrering, skapa en ny struktur med alla elementen som uppfyller ett villkor (ta-bort-vokaler i Uppg 119, filtrera i Uppg 1A, filter Övn 21) reduktion (ackumulering), t ex summera alla talen i en sekvens (räkna i Uppg 1A, högre ordningens funktion reduce) back-tracking, sök först bestäm sedan (efter-sista i Uppg 205, backfind i Uppg 2C) generering, från ett värde skapa nästa (generate-list i Uppg 2E)