Hoofdstuk 6 Advanced Scheme

6.1 Scripts en modules

Copy-paste werkt prima voor kleinere stukken code, maar als je een tijdje met racket gewerkt hebt krijg je behoefte aan een manier om een compleet script uit te voeren. Hier zijn twee manieren om dat te doen:

6.1.1 load

Stel dat je dit programma in zijn geheel wilt uitvoeren:

(read-line) ; discard first ENTER
(define (yes-func) (displayln "Thanks for typing yes"))
(define (no-func) (displayln "What a pity"))
 
(displayln "Please type yes or no")
(define answer (read-line))
 
(if (string=? answer "yes") (yes-func) (no-func))

Dit kun je copy-pasten maar dan merk je dat (read-line) de ENTER van het copy-pasten gebruikt. Niet handig. Je kunt het beter in zijn geheel laten uitvoeren door het in een file "yes_or_no.rkt" te zetten:

(load "yes_or_no.rkt")

6.1.2 require en zelf maken van modules

Zet onderstaande code in "yes_or_no_module.rkt")

#lang racket
 
(provide yes-or-no)
 
(define (yes-func) (displayln "Thanks for typing yes"))
(define (no-func) (displayln "What a pity"))
 
(define (yes-or-no)
  (displayln "Please type yes or no")
  (if (string=? (read-line) "yes") (yes-func) (no-func)))

Nu kun je de functie yes-or-no gebruiken als je eerst de file inlaadt met require:

(require csd/yes_or_no_module)
(require "yes_or_no_module")

6.2 quasiquote

A quoted expression is not evaluated: '(a b c ,(+ 3 4) d e) (quote (a b c ,(+ 3 4) d e))

With a back-tick, the sub-expressions preceded by a comma will be evaluated. This way you can create something like a template:

`(a b c ,(+ 3 4) d e)

resulting in '(a b c 7 d e)

6.3 lambda

Lambda functions are unnamed functions. These are very useful in places where it is impossible to define a function with a name but requires the application of a function.

The generic form is like this

(lambda (x) (do-something-with x))
 
(lambda (x) (+ 5 x))

Suppose you want to apply a function to all elements in a list without knowing in advance which function. You can give the function as an argument to the top level function.

(define (plus lst f)
  (if (empty? lst) '()
    (cons (f (first lst)) (plus (rest lst) f))))
 
(plus '(1 2 3 4 5) (lambda (x) (* 10 x)))

Of course you can first define the function times-10 but with a lambda you don't have to.

6.4 variabel aantal argumenten

Meestal geef je een functie een vast aantal argumenten, maar soms wil je dat niet vooraf bepalen.

(define sum (lambda lst (+ lst)))

6.5 (begin)

Met (begin) kun je een aantal acties of functie-aanroepen groeperen. Dit is bijvoorbeeld handig op plaatsen waar je eigenlijk maar één ding tegelijk kunt uitvoeren zoals bijvoorbeeld bij de beide takken van een (if).

Voorbeeld: een functie die alle elementen van een lijst onder elkaar laat zien met tussenpozen van halve seconde.

Een eerste poging:

(define (show lst)
  (if (empty? lst) (displayln "DONE")
    (show (rest lst))))

Uitvoeren: (show '(a b c d e f))

Dit laat alleen DONE zien, maar niet de rest.

(define (show lst)
  (if (empty? lst) (displayln "DONE")
    (displayln (first lst))
    (sleep 0.5)
    (show (rest lst))))

Dit heeft als probleem dat if niet goed is afgesloten. Je mag bij if maar één actie doen in elke van de twee takken en in dit voorbeeld staan drie acties in de 'false' tak: displayln, sleep en show.

Nog een poging:

(define (show lst)
  (if (empty? lst) (displayln "DONE")
    (displayln (first lst)))
    (sleep 0.5)
    (show (rest lst)))

Nu is de if wel goed afgesloten, maar er is iets aan de hand dat moeilijk te zien is: het maakt niet uit of de lijst leeg is of niet, de functies sleep en show worden altijd uitgevoerd. En dat levert een fout op wanneer je aan first of rest een lege lijst geeft.

Tot slot de oplossing:

(define (show lst)
  (if (empty? lst) (displayln "DONE")
    (begin
      (displayln (first lst))
      (sleep 0.5)
      (show (rest lst)))))

Hier zie je dat begin drie acties bundelt: displayln, sleep en show.

6.6 apply

Stel dat we een functie hebben die n argumenten verwacht maar we willen hem aanroepen met een lijst met n elementen als parameters. Dat kan met apply.

apply verwacht een functie en een lijst als parameters. Hij zal de functie toepassen op alle elementen uit de lijst alsof het de parameters van de functie zijn.

Hiermee kun je heel eenvoudig een functie maken die alle getallen in een lijst bij elkaar optelt, dus '(+ 1 2 3 4 5) zou je met apply zo opschrijven:

(apply + '(1 2 3 4 5))

Nog een voorbeeld: een lijst opbouwen met append, maar niet zoals gebruikelijk met deel-lijsten als parameters, maar een lijst van lijsten als parameter:

Dus niet zo:

(append '(a b c) '(d e f) '(g))

maar zo:

(apply append '( (a b c) (d e f) (g)))

Een muzikaal voorbeeld: note-on die de drie parameters channel, pitch en velocity verwacht. Normaal gezien zou je die zo aanroepen:

(note-on 0 60 100)

Maar als je de parameters nou toevallig als lijst beschikbaar hebt kun je hem ook zo aanroepen:

(apply note-on '(0 60 100))

Wat is het voordeel? Als je een lijst met noten hebt waarbij iedere noot ook weer wordt gedefinieerd als een lijst met drie parameters dan hoef je niet elke parameter uit de noot te peuteren. Je zou note-on natuurlijk zo kunnen aanroepen:

(note-on (first note) (second note) (third note))

maar je ziet snel genoeg dat het volgende handiger is:

(apply note-on note)

Ook handig: '+' kan van zichzelf al met een variabel aantal argumenten overweg, dus de lijst in het volgende voorbeeld kan een willekeurige lengte hebben:

(apply + '(1 2 3 4)) ==> 10

6.7 assoc

Lijst met key-value pairs, assoc kan members zoeken

(define length-table '( (8 1) (6 2.) (4 2) (3 4.) (2 4) (1 8) ))
 
(define (transform-length l)
  (cadr (assoc l length-table)))

6.8 map

---beschrijving is nog bij lange na niet compleet--- (map proc lst ...+)

Applies proc to the elements of the lsts from the first elements to the last. The proc argument must accept the same number of arguments as the number of supplied lsts, and all lsts must have the same number of elements. The result is a list containing each result of proc in order.

proc wordt toegepast op ALLE eerste elementen van de lijsten, daarom moet proc net zo veel argumenten accepteren als er lijsten zijn. Als de lijsten niet allemaal even lang zijn stopt 'map' na het laatste element van de kortste lijst.

(define offset 5)
(define melody '(60 62 64 60 62 64))
(define (transpose el t) (+ el t))
(map (lambda (el) (transpose el offset)) melody)
 
(define (show el) el)
(map show melody)
 
(define (cube lijst)
 (map (lambda (x) (* x x x)) lijst))

Inproduct van twee vectoren uitrekenen: vermenigvuldig paarsgewijs alle elementen van twee vectoren en tel daarna alle elementen van de resulterende lijst bij elkaar op:

(define (dotproduct vector1 vector2)
  (apply + (map (lambda (a b) (* a b)) vector1 vector2)))

6.9 currying

Currying: express as a series of functions that each take only one argument, or part of the arguments.

curry_add in the example below returns a function (we could e.g. give it a name like add-5) that can be applied to add a fixed number to one given argument.

(define (curry-add x)
  (lambda (y) (+ x y)))
 
(define add-5 (curry-add 5))
(add-5 20)
 
; without intermediate define
((curry-add 5) 20)

6.10 foldl / foldr

(foldl proc init lst ...+)

Like map, foldl applies a procedure to the elements of one or more lists. Whereas map combines the return values into a list, foldl combines the return values in an arbitrary way that is determined by proc.

6.11 sort

Sorteren van een lijst:

(define (myless a b) (< (car a) (car b)))

(define (sort-event-list lst) (sort lst myless))

(define (note-compare note-a note-b) (< (caddr note-a) (caddr note-b)))

(sort onset-list note-compare)

6.12 vector

6.13 let en let* (under construction)

Je komt vroeg of laat een situatie tegen waarbij je binnen een functie extra informatie wilt bewaren om binnen die functie te gebruiken. Je hebt misschien al gemerkt dat je define niet overal in een functie mag neerzetten. In de meeste gevallen biedt let dan uitkomst.

Bij let kun je een aantal dingen definieren (net als met define) en daar vervolgens bewerking mee uitvoeren.

Neem het volgende geval: je wilt binnen een functie een random getal twee keer gebruiken: eerst om het met displayln te laten zien en daarna om het in een lijst te plaatsen.

(define (rndlist n)
  (if (= n 0) '()
    (begin
      (displayln (random 10))
      (cons (random 10) (rndlist (- n 1))))))
 
 
(define (rndlist n)
  (if (= n 0) '()
    (begin
      (define rndnumber (random 10)) ; dit mag niet op deze plaats
      (displayln rndnumber)
      (cons rndnumber (rndlist (- n 1))))))
 
 
(define (rndlist n)
  (define rndnumber (random 10)) ; hier mag het wel maar soms weet je het hier nog niet
  (if (= n 0) '()
    (begin
      (displayln rndnumber)
      (cons rndnumber (rndlist (- n 1))))))
 
 
(define (rndlist n)
  (if (= n 0) '()
    (let ((rndnumber (random 10)))
      (begin
        (displayln rndnumber)
        (cons rndnumber (rndlist (- n 1)))))))

Een inzichtelijk voorbeeld van de algemene vorm van let is:

(let ((a 4) (b 5) (c 6) ...) (+ a b c))

Je ziet hier dat er eerst een rijtje 'defines' staat en daarna een functie die daar iets mee kan doen.

Let :-) op als de volgorde van creatie belangrijk is. Stel bijvoorbeeld dat b afhankelijk is van a, als volgt:

(let ((a 4) (b (- a 2))) (/ a b))

Dit gaat niet goed omdat je niet mag aannemen dat de 'defines' worden uitgevoerd in de volgorde waarin je ze hebt opgeschreven. Het kan dus best zijn dat eerst b een waarde krijgt, maar omdat a dan nog niet bestaat krijg je dan een fout. Gebruik in zo'n geval let*

(let\* ((a 4) (b (- a 2))) (/ a b))

6.14 Multiple return values

6.15 eval

N.B.: In de terminal-versie werkt het zoals je mag verwachten.

Eval evalueert letterlijke expressies.

(eval '(+ 4 5))
(eval (quote (+ 4 5)))

Een toepassing van eval

(define kick 27)
(define snare 38)
 
(eval 'kick)

Zoek de verschillen:

(define drum-sequence (list kick snare snare snare kick))
(define drum-sequence (eval '(list kick snare snare snare kick)))

In de eerste regel maak je een lijst met getallen. In de tweede regel maak je een lijst met symbolen die door eval wordt omgezet in een lijst met getallen. -duh- ?

Het grote voordeel van de tweede regel is dat je in je programma met symbolen kunt werken en pas op het laatste moment de representatie in getallen uitrekent. Dat zorgt voor meer duidelijkheid in je code.

6.16 gvector: Growable vector

Very useful for creating dynamically growing stuctures

(require data/gvector)
 
(define gv (make-gvector))
 
(gvector-add! gv (random 10))
(gvector-add! gv (random 10))
(gvector-add! gv (random 10))
(gvector-add! gv (random 10))
 
(gvector-count gv)
(gvector->list gv)
 
(gvector-remove-last! gv)
 
(gvector-count gv)
(gvector->list gv)

custodian