TDDC74 - Lektionsmaterial C Lektioner innehåller uppgifter av varierande slag. En del är mer diskussionsartade, andra mer experimentella. Ni behöver inte lämna in eller visa upp lösningarna på dessa för att bli klara med labbserien. Däremot är det en god idé att arbeta med dem för att få öva, och för att få en djupare förståelse för hur språk[et] kan fungera. De flesta övningarna här är inte tänkta som individuell mängdträning eller att öva in mönster. Vissa kan vara lite klurigare och kräva en stunds tänkande. Ni förväntas inte ha gjort klart allt under passet. I detta lektionsmaterial kommer du att utforska hur bindningar och scope fungerar i Racket (liksom många andra språk) analysera hur lexical closures kan användas för att skapa objekt. hantera strukturer som vi kan ändra inuti. översätta mellan olika rekursiva modeller, och översätta till en mer imperativ programmeringsstil. 1 Procedurobjekt och scopes Uppgift 1 ----------------------------------------------------------------------- Vi ges följande kod: (define name "Hugo") (define (switch! new-name) (if (eqv? new-name "GOOD") (set! name "GROOTIUS") (set! name "Nah"))) 1
(define (change-name! name) (if (eqv? name "GOOD") (set! name "Troll") (set! name "Nah"))) Vad borde name ha för värde efter att (switch! "GOOD") evaluerats? Vad borde name ha för värde efter att (change-name! "GOOD") evaluerats? Skriv ned din förutsägelse, och testa. Varför ser det ut som det gör? Om du vill kan du rita ett omgivningsdiagram för att förklara detta för dig själv. Uppgift 2 ----------------------------------------------------------------------- Vi skriver in uttrycken nedan (utan att evaluera dem): (define phil "Socrates") (define (run f phil) (printf "In run, phil=~a.~n" phil ) (f)) (define (g) (printf "In g, phil=~a.~n" phil) phil) (define run-retval (run g "Popper")) Vad förväntar du dig kommer att skrivas ut när vi kör koden (och i vilken ordning?)? Vad skrevs faktiskt ut? Varför? Vad förväntar du dig att run-retval ska ha för värde? Vilket värde fick det? Uppgift 3 ----------------------------------------------------------------------- Vi börjar nu bygga med lexical closures, där vi buntar ihop omgivningar (med lokala bindningar) med procedurer som når dem. Betrakta följande kod: (define (make-animal name) (lambda args 2
(cond [(null? args) (printf "Hello, I am ~a~n" name)] [(eqv? (car args) change-name!) (set! name (cadr args))] [else (printf "What?~n")]))) Lista ut svaren nedan, tillsammans med klasskamrater: a) Vad gör den? Vilket sorts värde returneras från make-animal? Symbol? Sträng? Funktion? Siffra? Osv. b) Vad kan man använda returvärdet till? Hur fungerar det? c) Om vi utför följande steg, vilka är returvärdena (eller utskrifterna)? > (define ox (make-animal "Tom J")) > ox ;; just to see what kind of value this is > (ox) > (ox change-name! "Thomas A") > (ox) d) Evaluera sedan följande (utan att ladda om så att ox-bindningen försvinner): > (define hedgehog (make-animal "Sonic")) > (hedgehog) > (hedgehog change-name! "Isaiah B") > (ox) ;; har ox ändrats? Notera att vi skapade ox och hedgehog på samma sätt. Varför påverkar inte de båda change-name! -anropen varandra, så att t ex ox presenterar sig själv som Isaiah B? Vi kommer att fortsätta detta arbete senare. 3
2 Muterbara strukturer I senare versioner av Scheme 1 finns det två sorts par. Dels immuterbara, som man inte kan ändra i, och muterbara. Muterbara cons-celler skapas med mcons (notera m:et). Man använder mcar, mcdr för att ta ut delarna. Om man vill ändra vad första elementet i ett muterbart par pekar på, använder man set-mcar! (och motsvarande för mcdr). Lägg till (require racket/mpair), så får du tillgång till funktioner som mlist, mlist?, mappend, mreverse, mmap. mmember. Skriv (print-as-expression #f) för att få läsbara listor i DrRacket. OBS I kursboken står det bara cons, set-car! och så vidare. Kom ihåg require-raden, lägg till ett m innan procedurnamnen ((m cons, set-m car!,... ) så fungerar det. Uppgift 4 ----------------------------------------------------------------------- Vi evaluerar dessa uttryck i tur och ordning: (define *list* (mcons fir (mcons pine ()))) (set-mcar! *list* spruce) (define *new-list* (mcdr *list*)) (set-mcdr! (mcdr *list*) (mcons birch (mcons oak ()))) *new-list* *list* (eq? *new-list* *list*) (eq? *new-list* (mcdr *list*)) Rita box-pointer-diagram för att illustrera hur strukturerna skapas, namnges, och ändras efter varje nytt uttryck. Rita inte nya strukturer om det inte skapas något nytt (det vill säga, om något ändras i ett par, stryk över det som ändrats och skriv till det nya). Uppgift 5 ----------------------------------------------------------------------- Vi skapar en procedur som tar ett muterbart par och ändrar det första elementet till siffran 1. Det ska fungera så här: > (require racket/mpair) 1 R 6 RS och framåt, inklusive Racket 4
> (define my-pair (mcons x y)) > my-pair { x. y } > (one-car! my-pair) > my-pair { 1. y } a) Vi försöker med detta: (define (one-car! p) (set! p (mcons 1 (mcdr p)))) Varför fungerar inte det? (testa exemplet ovan!) b) Skriv om one-car! så att den fungerar som den ska. Uppgift 6 ----------------------------------------------------------------------- Vi vill skapa en procedur insert! som tar en muterbar lista (mlista) mlst som har något innehåll, ett element elem och en position pos (en siffra) och lägger in elementet efter den positionen i listan. Vi kan räkna med att pos inte ligger utanför listans gränser. Första elementet i listan har position 0. a) Rita upp ett par exempel på hur detta kommer att fungera. Exempelvis: hur ser mlistan {a b c d} ut som box-pointer-diagram? Om du ska lägga in talet efter position 2 i listan, vad behöver tillkomma? Vilken/vilka av pekarna kommer att behöva ändras? Rita ut förändringen i diagrammet (stryk och rita till pekare och annat som behövs). b) Vad skulle hända om vi behöver lägga in elementet sist i listan? c) Skriv en procedur add-after som tar en mcons-cell i listan och ett element, och lägger till elementet efter det nuvarande. > (require racket/mpair) > (define lst (mlist a b c)) > lst ; om jag glömt skriva print-as-expression (mcons a (mcons b (mcons c ()))) > (print-as-expression #f) > lst 5
{a b c} > (add-after lst 999) > lst {a 999 b c} > (define lst-2 (mlist x y z)) > (add-after (mcdr lst-2) 112) > lst-2 {x y 112 z} d) Skriv proceduren insert! enligt ovan. Så här ska den fungera: > (define my-mlist (mlist 0 1 2 3 4)) > my-mlist {0 1 2 3 4} > (insert! my-mlist yo 2) > my-mlist {0 1 2 yo 3 4} 3 Iterativa procedurer och tilldelning Uppgift 7 ----------------------------------------------------------------------- Nedan har vi en linjärrekursiv funktion merge som tar två sorterade listor, och returnerar en sorterad lista som innehåller elementen i listorna (i ordning). a) Översätt koden nedan till en rekursiv procedur som ger upphov till en iterativ process (annorlunda: som har en iterativ processlösning): (define (merge lst1 lst2) (cond [(null? lst1) lst2] [(null? lst2) lst1] [(< (car lst1) (car lst2)) (cons (car lst1) (merge (cdr lst1) lst2))] [else (cons (car lst2) (merge lst1 (cdr lst2)))])) 6
Som kontrollfråga: vad är det som säger att det inte är en iterativ processlösning redan? Du kan behöva en funktion i stil med add-last (som lägger till element sist i en lista). Det är också tillåtet att använda den inbyggda funktionen reverse. b) Översätt den till en iterativ procedur där vi istället för att passa med värden som förändras som parametrar 2 har lokala bindningar som vi uppdaterar med hjälp av set!. 4 Lexical closures II Uppgift 8 ----------------------------------------------------------------------- Vi fortsätter make-animal-uppgiften ovan med att bygga ett objekt som hänvisar till andra objekt. En slags behållare. Det är helt OK att snegla på koden för make-animal! e) Definiera en procedur make-farm som fungerar som en behållare för olika djur enligt nedan: > (define oldmcdonalds (make-farm)) > oldmcdonalds #<procedure> > (define kolmarden (make-farm)) > (oldmcdonalds add-animal! (make-animal "Niccolo the Fox")) > (oldmcdonalds add-animal! (make-animal "Teenage Mutant Zeno Turtle")) > (oldmcdonalds) Presenting: Hello, I am Teenage Mutant Zeno Turtle Hello, I am Niccolo the Fox > (kolmarden) ;; inga djur tillagda här Presenting: > (oldmcdonalds animals) ;; returnera lista med själva djuren (#<procedure> #<procedure> #<procedure>) Fall som detta ska inte bli något problem: 2 Som man t ex passade med summan-hittills i sum-iter till exempel 7
> (define maxwell (make-animal "Maxwell the Demon")) > (oldmcdonalds add-animal! maxwell) > (kolmarden add-animal! maxwell) > (oldmcdonalds) Presenting: Hello, I am Maxwell the Demon Hello, I am Teenage Mutant Zeno Turtle Hello, I am Niccolo the Fox > (kolmarden) Presenting: Hello, I am Maxwell the Demon Ett par tips: Ett strategiskt placerat define (eller let) är användbart för att skapa lokala bindningar som bara gäller för just den funktion som vi skapar. (Varför vill man inte placera det innanför (lambda args...)?) Det finns en procedur for-each 3 som kan göra att du slipper skriva lite kod. 3 Leta på https://docs.racket-lang.org/reference/pairs.html. Se snabblänkarna i vänsterspalten, ON THIS PAGE:. 8