Ohjelmoinnin peruskurssien laaja oppimäärä Luento 10: Paikalliset muuttujat, kirjan tulkki kokonaisuutena (mm. SICP 3.2, 4.1.24.1.6) Riku Saikkonen 22. 11. 2012
Sisältö 1 Ympäristöt: miten paikalliset muuttujat toimivat (SICP 3.2) 2 Paikallisten muuttujien toteuttaminen tulkkiin 3 Kirjan tulkki valmiina kokonaisuutena
Mitä tässä tehdään? ympäristö on tapa (tulkin tietorakenne), jolla paikalliset muuttujat toteutetaan niiden avulla voi myös ymmärtää kielen toimintaa tarkemmin, erityisesti miten toistensa sisällä määritellyt proseduurit toimivat ympäristöt toimivat samalla tavalla useimmissa ohjelmointikielissä tarkemmin sanoen ne kertovat, miten leksikaalinen sidonta toimii mutta monissa kielissä ympäristöjä ei voi käyttää yhtä monipuolisesti kuin Schemessä (esim. monissa kielissä proseduureja ei voi määritellä sisäkkäin) ympäristö on eri asia kuin kutsupino: ympäristö kertoo, mistä nyt näkyvien muuttujien arvot haetaan kutsupino kertoo, mitä tehdään tämän funktion jälkeen alempana kutsupinossa on usein viittauksia toisiin ympäristöihin (joissa on usein yhteisiä osia) monissa kielissä (mm. C) kutsupinossa on myös muuttujien arvoja (silloin usein esim. palautettuja funktioita ei tueta)
Mikä on ympäristö? (SICP 3.2) Esimerkkikoodi (let ((x 1) (y 2)) (let ((a (+ x y)) (b (+ y y))) (* y a))) lauseketta (* y a) evaluoidessa sen muuttujat haetaan ympäristöstä, jossa on (ainakin) kolme kehystä: sisin kehys: a = 3, b = 4 toinen kehys: x = 1, y = 2 globaali ympäristö: * = kertolaskuprimitiivi,... muuttujan arvoa haettaessa sen nimeä etsitään ympäristöstä sisimmästä kehyksestä ulospäin nämä kehykset tulevat lauseketta ympäröivästä koodista ei siis niistä proseduureista, joista koodia on tällä kertaa kutsuttu (se olisi dynaamista eikä leksikaalista sidontaa) let:llä määritellyt paikalliset muuttujat ja proseduurien argumentit ovat ympäristöjen kannalta samanlaisia
Proseduurit ja ympäristöt: tallennus (SICP 3.2.3) Koodiesimerkki (define (make-counter val) (define f (make-counter 1)) (lambda (add) (define g (make-counter 2)) (set! val (+ val add)) (f 3) 4 val)) (g 4) 6 mitä muuttujien f ja g arvoissa pitää olla tallessa? proseduurin koodi (molemmissa sama (lambda (add)...)) ja ympäristö, jossa proseduuri on määritelty f:ssä on tallessa ympäristö, jossa on kaksi kehystä: sisin kehys F 1 : val = 1 globaali ympäristö F 0 : +, make-counter jne. g:ssä on viittaus samaan F 0 :aan, mutta eri sisin kehys: sisin kehys F 2 : val = 2 globaali ympäristö F 0 : +, make-counter jne.
Proseduurit ja ympäristöt: kutsuminen (SICP 3.2.2, 3.2.3) Koodiesimerkki (define (make-counter val) (define f (make-counter 1)) (lambda (add) (define g (make-counter 2)) (set! val (+ val add)) (f 3) 4 val)) (g 4) 6 f:ää kutsuttaessa siihen talletetun ympäristön eteen (sisemmäksi) tulee vielä yksi kehys, jossa on argumentti add ja sen arvo; esim. uusi sisin kehys F 3 : add = 3 (f 3): toinen kehys F 1 : val = 1 globaali ympäristö F 0 : +, make-counter jne. huom. proseduurikutsu tehdään siis käyttäen proseduurissa tallessa olevaa ympäristöä (eli sen määrittely-ympäristöä), ei kutsun tehneen koodin ympäristöä f:n koodin set! muuttaa kehyksessä F 1 olevaa val-muuttujan arvoa (sama kehys säilyy tallessa f:ssä)
Sisältö 1 Ympäristöt: miten paikalliset muuttujat toimivat (SICP 3.2) 2 Paikallisten muuttujien toteuttaminen tulkkiin 3 Kirjan tulkki valmiina kokonaisuutena
Ympäristöt kirjan tulkeissa (SICP 4.1.3) ympäristö on toteutettu listana kehyksiä sisimmästä uloimpaan; kehys on (cons lista muuttujien nimistä lista arvoista ) kirjassa määritellään proseduurit ympäristöjen käsittelyyn: lookup-variable-value, set-variable-value! ja define-variable! (extend-environment vars vals base-env ) laajentaa base-env iä uudella kehyksellä (ei muuta alkuperäistä) ja kehysten käsittelyyn mm: (add-binding-to-frame! var val frame ) lisää kehykseen uuden muuttujan nämä vain käsittelevät ympäristö-tietorakennetta: (list (cons '(a b) '(3 4)) ; sisin kehys (cons '(x y) '(1 2)) ; toinen kehys (cons '(*...) '(<primitiivi>...))) ; globaali ympäristö joka voitaisiin tehdä tähän tapaan: (extend-environment (list 'a 'b) (list 3 4) (extend-environment (list 'x 'y) (list 1 2) the-global-environment))
Ympäristöjen toteutus 1/3 (SICP 4.1.3) Apuproseduurit kehysten ja ympäristöjen käsittelyyn (define (make-frame variables values) (cons variables values)) (define (frame-variables frame) (car frame)) (define (frame-values frame) (cdr frame)) (define (add-binding-to-frame! var val frame) (set-car! frame (cons var (car frame))) (set-cdr! frame (cons val (cdr frame)))) ;; ympäristön sisin kehys ja ''loput kehykset'' eli ympäröivä ympäristö (define (first-frame env) (car env)) (define (enclosing-environment env) (cdr env)) ;; tekee uuden kehyksen sisimmäksi kehykseksi (define (extend-environment vars vals base-env) (if (= (length vars) (length vals)) (cons (make-frame vars vals) base-env) (if (< (length vars) (length vals)) (error "Too many arguments supplied" vars vals) (error "Too few arguments supplied" vars vals)))) m-eval.scm
Ympäristöjen toteutus 2/3 (SICP 4.1.3) Uuden muuttujan määritteleminen (define (define-variable! var val env) (let ((frame (first-frame env))) (define (scan vars vals) (cond ((null? vars) ; normaali tapaus: tee uusi muuttuja (add-binding-to-frame! var val frame)) ((eq? var (car vars)) (set-car! vals val)) (else (scan (cdr vars) (cdr vals))))) (scan (frame-variables frame) (frame-values frame)))) m-eval.scm normaalisti define-variable! lisää uuden muuttujasidonnan env:n sisimpään kehykseen (ei katso ulompia kehyksiä) jos muuttuja on jo olemassa sisimmässä kehyksessä, define-variable! muuttaakin sen arvoa eikä määrittele uutta Schemen define toimii näin (tosin tätä harvoin käytetään) ilman tätä sen voisi määritellä yksinkertaisesti: (define (define-variable! var val env) (add-binding-to-frame! var val (first-frame env)))
Ympäristöjen toteutus 3/3 (SICP 4.1.3) Olemassaolevien muuttujien käsittely m-eval.scm (define (lookup-variable-value var env) (define (env-loop env) (define (scan vars vals) (cond ((null? vars) (env-loop (enclosing-environment env))) ((eq? var (car vars)) (car vals)) (else (scan (cdr vars) (cdr vals))))) (if (eq? env the-empty-environment) (error "Unbound variable" var) (let ((frame (first-frame env))) (scan (frame-variables frame) (frame-values frame))))) (env-loop env)) (define (set-variable-value! var val env) (define (env-loop env) (define (scan vars vals) (cond ((null? vars) (env-loop (enclosing-environment env))) ((eq? var (car vars)) (set-car! vals val)) (else (scan (cdr vars) (cdr vals))))) (if (eq? env the-empty-environment) (error "Unbound variable -- SET!" var) (let ((frame (first-frame env))) (scan (frame-variables frame) (frame-values frame))))) (env-loop env))
Ympäristön alustus (SICP 4.1.4) tehdään vielä globaalin ympäristön alustaminen abstraktimmin eli extend-environment:lla eikä suoraan consilla Lopullinen setup-environment m-eval.scm (define the-empty-environment '()) (define (setup-environment) (let ((initial-env ;; oli: (cons (primitive-procedure-names) ;; (primitive-procedure-objects)) (extend-environment (primitive-procedure-names) (primitive-procedure-objects) the-empty-environment))) (define-variable! 'true true initial-env) (define-variable! 'false false initial-env) initial-env)) (define the-global-environment (setup-environment))
Proseduurit ja lambda ympäristöillä (SICP 4.1.2, 4.1.3) Apufunktiot proseduurien ja lambdan käsittelyyn (define (lambda? exp) (tagged-list? exp 'lambda)) (define (lambda-parameters exp) (cadr exp)) (define (lambda-body exp) (cddr exp)) (define (make-lambda parameters body) (cons 'lambda (cons parameters body))) m-eval.scm (define (compound-procedure? p) (tagged-list? p 'procedure)) (define (procedure-parameters p) (cadr p)) (define (procedure-body p) (caddr p)) (define (procedure-environment p) (cadddr p)) (define (make-procedure parameters body env) (list 'procedure parameters body env)) lambda-lausekkeen argumentteihin pääsee nyt käsiksi tulkkiin määriteltyyn proseduuriin talletetaan argumenttien nimet ja proseduurin määrittely-ympäristö
Ympäristöt evaliin ja applyyn (SICP 4.1.1) Muutokset evalissa ja applyssä m-eval.scm (define (eval exp env) (cond... ((lambda? exp) (make-procedure (lambda-parameters exp) (lambda-body exp) env))... ((application? exp) (apply (eval (operator exp) env) (list-of-values (operands exp) env) ;; ei enää env-argumenttia applylle ))... )) (define apply-in-underlying-scheme apply) (define (apply procedure arguments) ; ei env-argumenttia (cond ((primitive-procedure? procedure) (apply-primitive-procedure procedure arguments)) ((compound-procedure? procedure) (eval-sequence (procedure-body procedure) (extend-environment (procedure-parameters procedure) arguments (procedure-environment procedure)))) (else (error "Unknown procedure type -- APPLY" procedure))))
Parannus proseduurien tulostamiseen (SICP 4.1.4) Lopulliset käyttöliittymäproseduurit (define input-prompt ";;; M-Eval input:") (define output-prompt ";;; M-Eval value:") (define (prompt-for-input string) (newline) (newline) (display string) (newline)) (define (announce-output string) (newline) (display string) (newline)) m-eval.scm (define (driver-loop) (prompt-for-input input-prompt) (let ((input (read))) (let ((output (eval input the-global-environment))) (announce-output output-prompt) (user-print output))) ; oli display (driver-loop)) (define (user-print object) (if (compound-procedure? object) (display (list 'compound-procedure (procedure-parameters object) (procedure-body object) '<procedure-env>)) (display object)))
Sisältö 1 Ympäristöt: miten paikalliset muuttujat toimivat (SICP 3.2) 2 Paikallisten muuttujien toteuttaminen tulkkiin 3 Kirjan tulkki valmiina kokonaisuutena
Mitä kirjan tulkki tukee? kirjan kohtien 4.14.1.6 tulkki tukee Schemestä: proseduureja, muuttujia, argumentteja (yhtä monta kuin alla oleva), rekursiota, häntärekursiota, jne. erikoismuotoja quote, set!, define, if, lambda, begin ja cond (ei condin erikoisempia syntakseja) primitiivejä car, cdr, cons, null?, +, -, *, /, =, display, newline (uusia on helppo lisätä primitive-procedures-listaan) yleisimmin käytetyistä Schemen erikoismuodoista puuttuu esim. let, and ja or (kirjassa harjoitustehtävinä) myös tulkin virheidenkäsittely on melko yksinkertaista kohdassa 4.1.7 tehdään tästä tulkista hieman optimoidumpi versio, joka analysoi suoritettavan lausekkeen rakennetta etukäteen (ei käsitellä kurssilla)
Miten kirjan tulkkia käytetään? kirjan tulkin koodi löytyy kurssilaajennuksesta m-eval koodia kannattanee pitää esillä myös kirjaa lukiessa käyttö joko käyttöliittymällä: (driver-loop) tai suoraan evalia kutsumalla: (eval '(+ 1 2) the-global-environment) tai: (eval '(+ 1 2) (setup-environment)) globaalin ympäristön voi resetoida: (myös set! kävisi) (define the-global-environment (setup-environment)) kurssilaajennuksessa m-eval-display on versio, joka näyttää evalin ja applyn kutsut lyhyesti
Kirjan tulkin ajoesimerkki: (fact 0) (define (fact n) (if (= n 0) 1 (* n (fact (- n 1))))) ;;; M-Eval input: (fact 0) eval (fact 0) ; evalin haara ((application? exp) (apply...)) eval fact ; siinä kutsuttu list-of-values aiheuttaa tämän eval 0 ; ja tämän apply <procedure fact> 0 ; tästä (eval-sequence (procedure-body...)...) eval (if (= n 0) 1 (* n (fact (- n 1)))) eval (= n 0) ; (if (true? (eval (if-predicate exp) env))...) eval = ; taas application?-haara, joka tulee (= n 0):sta eval n eval 0 apply <primitive => 0 0 eval 1 ;;; M-Eval value: 1 ; (eval (if-consequent exp) env) ; ja 1 on self-evaluating?
Kirjan tulkin ajoesimerkki: (fact 1) eval (fact 1) eval fact ; arvo eli proseduuri haetaan lookup-variable-valuella eval 1 ; tämä taas on self-evaluating? apply <procedure fact> 1 eval (if (= n 0) 1 (* n (fact (- n 1)))) eval (= n 0) ; if-predicate eval = eval n eval 0 apply <primitive => 1 0 ; tässä välissä true? katsoo paluuarvoa #f eval (* n (fact (- n 1))) ; joten (eval (if-alternative exp) env) eval * eval n eval (fact (- n 1)) ; application?-haara: eval fact ; list-of-values aiheuttaa tämän eval (- n 1) ; ja tämän, jossa uusi application? eval - ; joka aiheuttaa nämä kolme eval n eval 1 apply <primitive -> 1 1 apply <procedure fact> 0... samoin kuin edellisessä esimerkissä 1 apply <primitive *> 1 1 ;; ei enempää evalin eikä applyn kutsuja, paluuarvo 1