Functionele argumenten

Sorteren

Laten we nog eens kijken naar de sorteer functie uit week 13. Hij sorteerde op achternaam. Als we nou een functie willen hebben die op voornaam sorteert, zouden we de twee functies "sorteer" en "alpha-first" moeten kopieren, en andere naam geven en "achternaam" door "voornaam" moeten vervangen:
...
(if (string
Als we op nummer willen sorteren zouden we opnieuw kopietjes van die functies moeten maken, maar dit keer met:
...
(if (< (nummer (car ls))
       (nummer (car rest)))
...

Dit kan handiger. Het toverwoord is "functionele argumenten". Het enige verschil tussen de drie sorteerfuncties boven was het gebruik van verschillende vergelijkings- en toegangsfuncties. We schrijven nu een algemene sorteer-functie en geven de specieke vergelijkings- en toegangsfunctie als argument mee:

(define sorteer    
   (lambda (ls vergelijking toegangsfunctie)
      (if (null? ls)
         '()
         (begin 
            (set! ls (alpha-first ls vergelijking toegangsfunctie))
            (cons (car ls)
                  (sorteer (cdr ls) vergelijking toegangsfunctie)
)  )  )  )  ) 
(define alpha-first
   (lambda (ls vergelijking toegangsfunctie)
      (if (equal? 1 (length ls))
         ls
         (let ((rest (alpha-first (cdr ls) vergelijking toegangsfunctie)))
            (if (vergelijking (toegangsfunctie (car ls))
                              (toegangsfunctie (car rest)))
               (cons (car ls) 
                     rest)
               (cons (car rest)
                     (cons (car ls)
                           (cdr rest)
)  )  )  )  )  )     )

>(define stud '( ( ( "Jacqueline" "Dake" ) 1 393) ( ( "Ashra" "Sugito" ) 2 575) ( ( "Jan" "Van Gerwen" ) 3 303) ( ( "Emiel" "Maarschalkerweerd" ) 4 676) ) )

> (sorteer stud string (sorteer stud string>? achternaam)
((("Jan" "Van Gerwen") 3 303) (("Ashra" "Sugito") 2 575) (("Emiel" "Maarschalkerweerd") 4 676) (("Jacqueline" "Dake") 1 393))

> (sorteer stud < nummer)
((("Jacqueline" "Dake") 1 393) (("Ashra" "Sugito") 2 575) (("Jan" "Van Gerwen") 3 303) (("Emiel" "Maarschalkerweerd") 4 676))

> (sorteer stud > punten)
((("Emiel" "Maarschalkerweerd") 4 676) (("Ashra" "Sugito") 2 575) (("Jacqueline" "Dake") 1 393) (("Jan" "Van Gerwen") 3 303))

Map etc.

Voor "apply", "map", "for-each" en flexibele aantallen argumenten in LAMBDA-expressies zie ook de uitleg op de pagina van Walter Daelemans.

Voorbeelden voor gebruik van "map":

> (map string-length '("as" "qwe" "" "irtuoyiuero" "pppp"))
(2 3 0 11 4)
> (map number? '(1 q 2 w 3 e r 4 5 t))
(#t #f #t #f #t #f #f #t #t #f)
> (map car '( (a s d) (f g h) (j k l) ) )
(a f j)
Deze functie kunnen we nu ook zelf schrijven:
(define my-map-1
   (lambda (func ls)
      (if (null? ls)
         '()
         (cons (func (car ls)
               )
               (my-map-1 func (cdr ls))
)  )  )  )
Hoe pas je "map" nou toe als je zoiets wilt als "tel bij elk element 1 op"?
> (define hulp-functie
     (lambda (i) 
        (+ 1 i)))
> (map hulp-functie '(1 2 3))
(2 3 4)
of korter met een anonieme functie:
> (map (lambda (i) (+ 1 i)) '(1 2 3))
(2 3 4)
Maar "map" kan meer. Tot nu toe hadden we het alleen gebruikt met functies die maar één argument namen. Hier zijn voorbeelden met meerdere argumenten:
> (map + '(1 2 3) '(4 5 6))
(5 7 9)
> (map + '(1 2 3) '(4 5 6) '(7 8 9))
(12 15 18)
> (map cons '(a b c) '((1 2) (3 4) (5 6)))
((a 1 2) (b 3 4) (c 5 6))
> (map equal? '(a s d f) '(a w e f))
(#t #f #f #t)
> (map +)
*** ERROR IN (stdin)@199.1 -- Wrong number of arguments passed to procedure
(map '#)
> (map + '(1 2 3) '(4 5 6) '(7 8))
*** ERROR IN (stdin)@201.1 -- Lists are not of equal length
(map '# '(1 2 3) '(4 5 6) '(7 8))
Ook dit kunnen we zelf schrijven (dat wordt wel een beetje ingewikkelder). Let op het gebruik van de nieuwe vorm van "lambda" en de toepassing van "apply":
(define my-map-n
   (lambda input                  ; LET OP: lambda met flexibel aantal argumenten
;      (display "input=") (display input) (newline)
      (let ((func (car input))
            (args (cdr input)))
         (if (null? (car args))
            '()
            (cons (apply func 
                         (my-map-1 car 
                                   args)
                  )
                  (apply my-map-n (cons func 
                                        (my-map-1 cdr 
                                                  args)
)  )  )  )  )     )               )
         
> (my-map-n + '(1 2 3) '(4 5 6) '(7 8 9))
(12 15 18)
En alsof het allemaal nog niet genoeg is kunnen we functies behalve als argumenten ook nog als "return values" gebruiken. Dit is de functie die een functie teruggeeft die zijn argument met een bepaald bedrag verhoogt:
> (define verhoog
     (lambda (met-wat)
        (lambda (argument)
           (+ met-wat argument))))

> (map (verhoog 1) '(1 2 3))
(2 3 4)
> (map (verhoog 5) '(1 2 3))
(6 7 8)
En we hoeven natuurlijk niet altijd op te tellen:
> (define doe-iets
     (lambda (wat met-wat)
        (lambda (argument)
           (wat met-wat argument))))

> (map (doe-iets + 1) '(1 2 3))
(2 3 4)
> (map (doe-iets - 5) '(1 2 3))
(4 3 2)                         ; nee, hier komt niet (-4 -3 -2) uit!
> (map (doe-iets string-append "de ") '("man" "vrouw" "hond" "kat"))
("de man" "de vrouw" "de hond" "de kat")

Opdracht

Schrijf een functie "filter" die een predikaat en een lijst neemt en (een lijst met) alle lijstelementen teruggeeft die het predikaat waar maken:
> (filter number? '(1 q 2 e 3 r 4 5 t y))
(1 2 3 4 5)
> (filter symbol? '(1 q 2 e 3 r 4 5 t y))
(q e r t y)
> (filter list? '(1 q 2 e 3 r 4 5 t y))
()
> (filter (lambda (ls) (not (null? ls))) '( (a s d) () (1 2) ("oiu") () ))
((a s d) (1 2) ("oiu"))