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:
load
om een heel script uit te voerenrequire
waarmee je de functies uit een module ter beschikking hebtStel 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")
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")
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)
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.
Meestal geef je een functie een vast aantal argumenten, maar soms wil je dat niet vooraf bepalen.
(define sum (lambda lst (+ lst)))
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.
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
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)))
---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)))
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)
(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.
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)
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))
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.
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)