Opdrachten bespreken

scoping problemen en implementatie

Ondertussen zijn we zo'n beetje voorbij de allereerste beginselen van Scheme. Dit gaat natuurlijk gepaard met weer geheel nieuwe, onduidelijke problemen. Zoals daar zijn:

  1. de een kan de 'car' nemen van een lege lijst, terwijl dat bij de ander een fout oplevert. Dit is gewoon "not done", of het nu werkt of niet, en ik zal hier dus verder niets over zeggen.
  2. de een kan variabelen middels 'define' een nieuwe waarde geven op een willekeurige plaats, de ander krijgt een fout.

Goed, we hebben dus ontdekt dat de ene scheme de andere niet is :) Maar hoe gaan we hier mee om? Het laatste punt is eenzogenaamd scoping-probleem. Scheme maakt onderscheid tussen locale en globale variabelen enerzijds, en gebruik van (sommige implementaties van Scheme) 'define', 'let', en 'set!' als manieren om de waarde van je variabele aan te passen. Scheme is uiteindelijk een functionele taal is, en dat betekent dat uitdrukkingen zoals '(define x (+ x 1))' uit den boze zijn. Maar hoe dan wel de waarde van een variabele aanpassen?
Zoals ik al aangaf, heeft Scheme hier 3 functies voor:
  1. define: het creeren van (globale of toplevel) variabelen (1 keer per variabele!)
  2. let: het tijdelijk (=locaal, i.e. binnen het let-blok) toekennen van een waarde aan een variabele (of die variabele nu gedefinieerd is of niet). Syntax:
    (let ((NAME1 VALUE1) (NAME2 VALUE2) ... ) let-body)
    waarbij de gebonden variabelen in de let-body de waarde hebben die in tussen 'let' en de let-body opgegeven zijn. Na de let-body hebben variabelen die al bestonden voor de let-clause hun oude waarde weer (of liever: die waarde was onzichtbaar, of out-of-scope) danwel bestaan niet meer als ze niet gedefinieerd waren.
  3. set!: set! wijzigt de waarde van een bestaande variabele. Dus een globale variabele verandert globaal, dus overal, van waarde, als gevolg van een set!-operatie. Syntax:
    (set! VARIABELE VALUE)

Voorbeelden voor set!

> (set! a 1)
[VM ERROR encountered!] SET! of an unbound variable
A
> (define a 1)
> (set a 2)
> a
2
> (define func (lambda (b) (set! b 4) (display b)))
> (define b 5)
> b
5
> (func 3)
4
> b
5
> (define func (lambda () (set! b 4) (display b)))
> (func)
> b
4
> (define func (lambda () (set! c 4) (display c)))
> (func)
[VM ERROR encountered!] SET! of an unbound lexical variable
C

Debugging

Er zijn verschillende redenen waarom een programma soms niet doet wat je verwacht/wil, en er zijn dus ook verschillende mogelijkheden om achter die redenen te komen.

Foutmeldingen

Foutmeldingen geven meestal aan wat er mis is en waarmee. Zie het voorbeeld van boven met "set!":
SET! of an unbound variable 
A
Hieruit leren we dat "set!" alleen maar gebruikt mag worden met variabelen die al eerder gebruikt zijn, en dat het probleem in dit geval bij de variabele "a" ligt.

Hier is een (nog niet volledige) lijst van PCSCHEME foutmeldingen.

Inspect

Als je een foutmelding krijgt kom je vanzelf bij de [INSPECT] prompt terecht. Tot nu toe zijn we er altijd snel weer uitgegaan (met Control-Q). Maar je kan ook de volgende commando's proberen:
[INSPECT] ?
 ctrl-A -- display All environment frame bindings
 ctrl-B -- display procedure call Backtrace
 ctrl-C -- display Current environment frame bindings
 ctrl-D -- move Down to callee's stack frame
 ctrl-E -- Edit variable binding
 ctrl-G -- Go (resume execution)
 ctrl-I -- evaluate one expression and Inspect the result
 ctrl-L -- List current procedure
 ctrl-M -- repeat the breakpoint Message
 ctrl-P -- move to Parent environment's frame
 ctrl-Q -- Quit (RESET to top level)
 ctrl-R -- Return from BREAK with a value
 ctrl-S -- move to Son environment's frame
 ctrl-U -- move Up to caller's stack frame
 ctrl-V -- eValuate one expression in current environment
 ctrl-W -- (Where) Display current stack frame
Als voorbeeld hier de (identieke) functies func2, func3 en func4 (staan ook in "S:/terug/defin.txt").
(define func2
   (lambda (ls)
      (if (null? ls)
          '()
          (cons (+ 1 
                   (car ls))
                (func3 (cdr ls))))))
(define func3
   (lambda (ls)
      (if (null? ls)
          '()
          (cons (+ 1 
                   (car ls))
                (func4 (cdr ls))))))
(define func4
   (lambda (ls)
      (if (null? ls)
          '()
          (cons (+ 1 
                   (car ls))
                (func4 (cdr ls))))))

> (func2 '(1 2 3))
(2 3 4)
> (func2 '(1 2 a))
[VM ERROR encountered!] Non-numeric operand to arithmetic operation (+ 1 A)

[INSPECT] CTRL-C
Environment frame bindings at level 0
    LS                   -- list --

[INSPECT] CTRL-L
#<PROCEDURE FUNC4> =
(lambda (ls)
  (if (null? ls)
      '()
      (cons (+ 1 (car ls)) (func4 (cdr ls)))))

[INSPECT] CTRL-B
Stack frame for #<PROCEDURE FUNC4>
    LS                   -- list --
  called from   #<PROCEDURE FUNC3>
  called from   #<PROCEDURE FUNC2>
  called from   ()
  called from   #<PROCEDURE EVAL>
  called from   #<PROCEDURE ==SCHEME-RESET==>

[INSPECT] CTRL-M
[VM ERROR encountered!] Non-numeric operand to arithmetic operation (+ 1 A)

[INSPECT] CTRL-V
Value of: ls
(A)

Display

Een veel gebruikte truc is om op belangrijke punten in je programma tekst op het scherm te laten drukken die aangeeft waar het programma zich net bevindt en wat de waarde van belangrijke variabelen op dat moment is. Zie deze variant van "func4" van hierboven.
(define func4-2
   (lambda (ls)
      (display "ls=")
      (display ls)
      (if (null? ls)
          (begin 
             (display " -> stopconditie bereikt")
             (newline)
             '() 
          )
          (begin 
             (display " -> recursie")
             (newline)
             (let ((first (car ls))
                   (rest (func4-2 (cdr ls))))
                (display "first=")
                (display first)
                (display " rest=")
                (display rest)
                (newline)
                (cons (+ 1 first)
                      rest))))))

> (func4-2 '(1 2 3))
ls=(1 2 3) -> recursie
ls=(2 3) -> recursie
ls=(3) -> recursie
ls=() -> stopconditie bereikt
first=3 rest=()
first=2 rest=(4)
first=1 rest=(3 4)
(2 3 4)

Trace

Een soortgelijk effect bereik je met de functie "trace" (met "untrace" zet je hem weer uit):
> (func4 '(a b c))
A
B
C
()
> (trace func4)
OK
> (func4 '(a b c))
 >>> Entering #<PROCEDURE FUNC4>
  Argument 1: (A B C)
A
 >>> Entering #<PROCEDURE FUNC4>
  Argument 1: (B C)
B
 >>> Entering #<PROCEDURE FUNC4>
  Argument 1: (C)
C
 >>> Entering #<PROCEDURE FUNC4>
  Argument 1: ()
()
> (untrace func4)
OK
> (func4 '(a b c))
A
B
C
()

Opdrachten

Schrijf zo klein mogelijke functies waarbij elke functie een andere foutmelding produceert. Stuur de lijst functies en bijbehorende foutmeldingen op.