sicp/zapiski/sicp-lio.org

22 KiB

Structure and Interpretation of Computer Programs

Foreword and Preface

Lisp je preživeli, v uporabi je že "polovico stoletja".

The discretionary exportable functionality entrusted to the individual Lisp programmer is more than an order of magniture greater than that to be found within Pascal enterprises.

Želimo vzpostaviti idejo, da programski jezik ni samo način, da računalnik izvaja operacije, ampak da je predvsem nov formalni medij za izražanje idej o metodologiji. Zato morajo biti programi napisani predvsem zato, da jih ljudje berejo, in slučajno, da jih izvajajo računalniki.

Bistvena tema ni sintaksa določenih struktur v programskem jeziku, niti …, temveč tehnike nadzora intelektualne kompleksnosti veliki programskih sistemov.

Naš pristop k temi izvira iz prepričanja, da "computer science" ni znanost in da ima njen pomen bolj malo opraviti z računalniki. Računalniška revolucija je revolucija v načinu mišljenja in izražanju idej. Bistvo teh sprememb najbolše opiše pojem proceduralne epistemologije, ki se ukvarja s strukturo vednosti z imperativnega stališča za razliko od klasične matematike, ki je bolj deklerativna. Matematika postavi okvir za natančno spoprijemanje s pojmovanjem "kaj je". Računanje pa ponudi okvir za natančno ukvarjanje s pojmovanjem "kako".

Grajenje abstrakcij s procedurami

Elementi programiranja

Primitivni izrazi
predstavtljajo najpreprostejše gradnike (entitete) programskega jezika
Načini kombinacije,
s katerimi so sestavljeni elementi zgrajeni iz preprostejših
Načini abstrakcije,
s katerimi so lahko sestavljeni elementi poimenovani in omogočajo upravljanje z njimii kot enotami

Izvajanje kombinacij(e)

Postopek za izvajanje kombinacij:

  1. Izvedi podizraz kombinacije.
  2. Uporabi/uveljavi proceduro, ki je najbolje levi podizraz (operator) z argumenti, ki so vrednosti drugih podizrazov (operandi).

Postopek evalvacije je rekurziven, saj drugi korak v sebi vključuje prvega, oziroma vključuje svojo definicijo.

Tako se zgradi akumulacijsko drevo. Na koncu vedno prideš do točke, ko izvajaš primitivne izraze, ki so:

  • vrednosti numeričnih števk, ki jo označujejo.
  • vrednosti vgrajenih operatorjev so strojni ukazi sekvenc, ki izvedejo te operacije.
  • vrednosti drugih imen so objekti asociirani s temi imeni v okolju.

Drugo pravilo je poseben primer tretjega pravila. Simboli + in * so tudi vključeni v globalno okolje in so asociirani s strojnimi ukazi, ki so njihove vrednosti. Pomembno je prepoznati vlogo okolja pri določanju pomena simbolov v izrazih.

To pravilo se ne nanaša na posebne oblike (special forms). define je posebna oblika.

1.1.4 Sestavljene procedure

  • Številke in aritmetične operacije so primitivni podatki in procedure.
  • Gnezdenje kombinacij omogoča način za združevanje operacij.
  • Definicije, ki asociirajo imena z vrednostmi omogočajo omejene načine abstrakcije.

(define (square x) (* x x))

(define square (lambda (x) (* x x)))

1.1.5 Substitucijski model za izvajanje procedur

Za izvajanje sestavljenih procedur z argumenti, izvedeš telo procedure z vsakim formalnim parametrom, ki ga nadomestiš s pripadajočim argumentom.

ergh, tukaj se zapletam s slovenskimi prevodi

kaj je application in kaj evaluation?

Načini, na katere deluje interpreter (prevajalnik):

Aplikativni vrstni red
Najprej evalviraj operator in operande, potem pa izvedi proizvedeno proceduro s pridobljenimi argumenti.
Normalni vrstni red
Ne izvajaj operandov dokler njihove vrednost niso potrebne. Najprej zamenjaj izraze operandov s parametri, dokler ne pride do izraza, ki vsebuje zgolj primitivne izraze in potem izvedi (vso) evalvacijo.

vaje

1.3

najprej narobe

Define a procedure that takes three numbers as arguments and returns the sum of the squares of the two larger numbers.

  (define (sum-of-large x y z)
    (+
     (if (> x y) (* x x) (* y y))
     (if (> y z) (* y y) (* z z))
     )
    )
  (sum-of-large 3 8 5)
128
  (define (sum-of-larger x y z) (let*
                                  ((s (lambda (a) (* a a)))
                                   (sl (lambda (b c) (if (> b c) (s b) (s c))))
                                   )
                                (+ (sl x y) (sl y z))
                                ))
  (sum-of-larger 3 8 5)
128
pravilno
  (define (sum-squares-of-larger x y z)
    (if (> x y)
        (if (> y z)
            (+ (* x x) (* y y))
            (+ (* x x) (* z z))
            )
        (if (> x z)
            (+ (* y y) (* x x))
            (+ (* y y) (* z z))
            )
        )
    )
  (sum-squares-of-larger 9 10 8)
181

1.5

Aplikativni vrstni red: pade takoj v neskoncno zanko. Normalni vrstni red: izvrsi test in pride v if, ki ne izvrsi drugega dela.

1.7

  • good-enough? ni vredu za iskanje korenov majhnih stevil.
  • pravtako za zelo velika stevila
  • napisi alternativno good-enough? proceduro, ki bo gledala, kdaj so spremembe dovolj majhne in takrat prekini funkcijo.

// Poglej v sqrt-newton.scm

1.8

// Glej v sqrt-newton.sqm

1.1.8 Procedure kot crne skatle abstrakcij

  • block structure
  • lexical scoping

1.2.2 Drevesna rekurzija

1.2.3 Redi rasti

1.2.4 Eksponentna funkcija

Tukaj se naucimu successive squaring, ki potem se veckrat prav pride.

#name: exponent

  ;; O(n) korakov in O(n) prostora
  (define (expt b n)
    (if (= n 0)
        1
        (* b (expt b (- n 1)))
        )
    )


  (define (expt-i b n)
    (expt-iter b n 1)
    )

  ;; O(n) korakov O(1) prostor
  (define (expt-iter b cnt prod)
    (if (= cnt 0)
        prod
        (expt-iter b (- cnt 1) (* b prod))
        )
    )

  (define (fast-expt b n)
    (cond
     ((= n 0) 1)
     ((even? n) (square (fast-expt b (/ n 2))))
     (else (* b (fast-expt b (- n 1))))
     )
    )

  (define (even? n) (= (remainder n 2) 0))

  (define (square x) (* x x))

  ;; 1.16
  ;; successive squaring (fast-expt) but with iteration.
  ;; transformation (* a (expt b n)) constant

  (define (fast-expt-i b n)
    (fast-expt-iter b n 1)
    )

  (define (fast-expt-iter b n a)
    (cond
     ((= n 0) a)
     ((even? n) (fast-expt-iter (square b) (/ n 2) a))
     (else (fast-expt-iter b (- n 1) (* a b)))
     )
    )

  ;; I'm not sure why this works. I was just guessing.

  ;; excersize 1.17
  (define (slow-multi a b)
    (if (= b 0) 0
        (+ a (* a (- b 1)))
        )

    )
  (define (halve x) (/ x 2))
  (define (double x) (* x 2))
  (define (fast-multi a b)
    (cond
     ((= b 0) 0)
     ((even? b) (double (fast-multi a (halve b))))
     (else (+ a (fast-multi a (- b 1))))
     )
    )

  ;; excersize 1.18
  (define (fast-multi-i a b)
    (fast-multi-iter a b 0)
    )
  (define (fast-multi-iter a b s)
    (cond
     ((= b 0) s)
     ((even? b) (fast-multi-iter (double a) (halve b) s))
     (else (fast-multi-iter a (- b 1) (+ s a)))
     )
    )

  ;; excercise 1.19 - fast fibnonachi
  (define (fast-fibo n)
    (fast-fibo-iter 1 0 0 1 n)
    )
  (define (fast-fibo-iter a b p q count)
    (cond ((= count 0) b)
          ((even? count)
           (fast-fibo-iter
            a
            b
            (+ (* p p) (* q q))
            (+ (* q q) (* 2 p q))
            (/ count 2)
            )
           )
          (else (fast-fibo-iter
                 (+ (* b q) (* a q) (* a p))
                 (+ (* b p) (* a q))
                 p
                 q
                 (- count 1)
                 ))
          )
    )
  ;; p' = q^2 + 2pq
  ;; p' = p^2 + q^2
  (define (slow-fibo n)
    (cond ((= n 0) 0)
          ((= n 1) 1)
          (else (+
                 (slow-fibo (- n 1))
                 (slow-fibo (- n 2))
                 ))
          )
    )
  ;; melje melje in melje . fast-fibo iypljune takoj

1.2.5 Najvecji skupni deljitel

1.2.6 Primer: Iskanje prastevil

1.3 Sestavljanje abstrakcij s procedurami visjega reda

Procedure, ki spreminjajo druge procedure se imenujejo procedure višjega reda.

1.3.1 Procedure kot argumenti

Primer vsote.

//exercise 1.29 #name: simpson

  (define (sum term a next b)
    (if (> a b)
        0
        (+ (term a)
           (sum term (next a) next b)
           )
        )
    )

  (define (integral f a b dx)
    (define (add-dx x) (+ x dx))
    (* (sum f (+ a (/ dx 2.0)) add-dx b) dx)
    )

  (define (sum-s term a next b fact)
    ;; fact is altering between 4 and 2
    (define (check-fact fact) (if (= fact 4) 2 4))
    (if (> a b)
        0
        (+ (* fact (term a))
        (sum-s term (next a) next b (check-fact fact))
        )
    )
    )

  (define (simpson f a b dx)
    (define (add-dx x) (+ x dx))
    (* (+ (f a) (f b) (sum-s f (add-dx a) add-dx (- b dx) 4) ) (/ dx 3.0))
    )

  (define (simpson-gizmo f a b dx)
    (define (add-dxdx x) (+ x dx dx))
    (* (+
     (* 4 (sum f (+ a dx) add-dxdx b))
     (* 2 (sum f a add-dxdx b))
     (- (f a))
     (- (f b))
     ) (/ dx 3.0))
    )

  (define (cube x) (* x x x))

  (list
   (integral cube 1 2 0.01)
   (integral cube 1 2 0.001)

   (simpson cube 1 2 0.01)
   (simpson cube 1 2 0.001)
   (simpson cube 1 2 (/ 1 1000))
   (simpson-gizmo cube 1 2 0.01)
   (simpson-gizmo cube 1 2 (/ 1 10000))
   (simpson-gizmo cube 1 2 0.00001)
  )
3.7499625000000045 3.7499996249995324 3.644925346666673 3.7499999999995324 3.749893334961112

// exercise 1.30

  (define (sum-i term a next b)
    (define (iter a result)
      (if (> a b)
          result
          (iter (next a) (+ result (term a)))
          )
      )
    (iter a 0)
    )

// excercise 1.30, 1.31. 1.32

  ;; Analogno napisi produkt kot vsoto.
  ;; Pokazi kako izgleda fakulteta.
  ;; Aproksimacija pi/4 = 2/3*4/3*4/5*6/5*6/7*8/7...

  (define (produkt-r term a next b)
    ;; a, b sta spodnja in zgornja meja
    (if (> a b)
        1
        (* (term a) (produkt-r term (next a) next b))
        )
    )

  (define (fakulteta-p n)
    (produkt-r (lambda (x) x) 1 (lambda (x) (+ x 1)) n)
    )

  (define (pribl-pi n)
    (produkt-r (lambda (a) (/ (* (- a 1.0) (+ a 1.0)) (* a a)))
               3.0
               (lambda (x) (+ x 2.0))
               n
               )
    )
  ;; gizmo se je spomnil resitve - dva produkta (zgornji in spodnji)

  ;; iterativni produkt-i
  (define (produkt-i term a next b)
    (define (iter-p a result)
      (if (> a b)
          result
          (iter-p (next a) (* result (term a)))
          )
      )
    (iter-p a 1)
    )

  (define (pribl-pi-term a)
    (/ (* (- a 1.0) (+ a 1)) (* a a)))

  (define (pribl-pi-next a) (+ a 2.0))

  (define (pribl-pii n)
    (produkt-i
     pribl-pi-term
     3.0
     pribl-pi-next
     n
     ))

  ;; excercise 1.32
  ;; recursive accumulate
  (define (accumulate-r combiner null-val term a next b)
  ;; combiner is a procedure of two arguments.
    (if (> a b)
        null-val
        (combiner (term a) (accumulate-r combiner null-val term (next a) next b))
        )
    )
  (define (sum-combiner t acc)
    (+ t acc)
    )
  (define (sum-a term a next b)
    (accumulate-r (lambda (t acc) (+ t acc)) 0 term a next b)
    )

  (define (prod-a term a next b)
    (accumulate-r (lambda (t acc) (* t acc)) 1 term a next b)
    )

  (define (accumulate-i combiner null-val term a next b)
    ;; Iterative accumulator.
    (define (iter-a a result)
      (if (> a b)
          result
          (iter-a (next a) (combiner (term a) result))
          )
      )
    (iter-a a null-val)
    )
  (define (identity x) x)
  (define (add1 x) (+ x 1))
  (define (sum-ai term a next b)
    (accumulate-i sum-combiner 0 term a next b)
    )
  (define (prod-ai term a next b)
    (accumulate-i (lambda (t acc) (* t acc)) 1 term a next b))
  (define (fakulteta-ai n) (prod-ai identity 2 add1 n))

  ;; excercise 1.33 filtered accumulate - combine only those term derived from
  ;; values in the range that satisfy a specified condition (predicate).
  ;; a) sum of squares of prime numbers - assuming prime? exists already

  (define (filtered-accumulate-r combiner null-val predicate term a next b)
    ;; combiner 2 args - element and accumulation
    ;; predicate 1 arg - a condition when to apply combiner
    ;; term      1 arg - a function to compute the term
    ;; next      1 arg - compute a next step
    (if (> a b)
        null-val
        (if (predicate a)
            (combiner (term a) (filtered-accumulate-r combiner null-val predicate term (next a) next b))
            ;; should I call combiner with null-val instead of (term a) or can I
            ;; directly call filtered-accumulate-r?
            (filtered-accumulate-r combiner null-val predicate term (next a) next b)
            )
        )
    )
  ;; (filtered-accumulate-r sum-combiner 0 even? identity 1 add1 11)
  (define (filtered-accumulate-i combiner null-val predicate term a next b)
    (define (iter-fa a result)
      (if (> a b)
          result
          (iter-fa (next a)
                   (if (predicate a)
                       (combiner (term a) result)
                       (combiner null-val result)
                       )
                   )
          )
      )
    (iter-fa a null-val)
    )

1.3.2 Sestavljanje procedur z Lambda

Splosna forma let izraza

(let ((<var1> <exp1>)
      (<var2> <exp2>)
      ...
      (<varn> <expn>))
   <body>)

To je okrajsava za

((lambda (<var1> ... <varn>)
    <body>)
  <exp1>
  ...
  <exp2>
)

1.3.3 Procedure kot splosne metode

Ce pogledamo proceduro za integral, vidimo mocnejse abstrakcije: procedure, ki izrazajo splosne racunske metode, neodvisne od posameznih vkljucenih funkcij.

1.3.4 Procedure kot vrnjene vrednosti

V splošnem programski jeziki omejujo, kateri komputacijski elemente lahko (koda) spreminja. Elementi z najmanj omejitvami imajo prvorazredni status. Pravice in privilegiji prvorazrednih elementov so:

  • lahko so poimenovani s spremenljivkami
  • lahko so podani kot argumenti procedur
  • lahko so vrnjeni kot rezultati procedur
  • lahko so vključeni v podatkovne strukture

V Lispu imajo, za razliko od drugih programskih jezikov, procedure prvorazredni status. To predstavlja težave za implementacijo, ampak nudi višjo ekspresivno moč programskega jezika. Najvišja cena pri implementaciji procedur s prvorazrednim statusom je, da je potrebno rezervirati prostor za procedurine proste spremenljivke tudi, ko se procedura ne izvaja. V scheme-u so te spremenljivke shranjene v procedurino okolje (poglavje 4.1).

Grajenje absrakcij s podatki

Poglavje bo govorilo o kompleksnih podatkih. Poglavje 1 govori o grajenju abstrakcij z zdruzevanjem procedur, ki tvorijo sestavljene procedure (compound). V poglavju 2 pa bo fokus na grajenju abstrakcij z zdruzevanjem podatkovnih objektov v sestavljene podatke (compound).

Z zdruzenimi podatkovnimi objekti lahko procedure delajo nad njimi ne da bi bile odvisne od njihove natancne strukture.

Podobno kot pri sestavljenih procedurah gre tudi pri sestavljenih podatkovnih objektih za nacin spoprijemanja s kompleksnostjo - podatkovne abstrakcije omogocijo postavitev primernih abstrakcijskih pregrad med razlicnimi deli programa.

Napoved, kaj se bo pregledalo v 2. poglavju (bi bilo smiselno povzet).

Uvod v podatkovne abstrakcije

Podatkovna abstrakcija je metodologija, ki nam omogoci, da locimo kako so sestavljeni podatki uporabljeni od detajlov o tem, kako so izgrejeni iz primitivnih podatkovnih objektov. (To je analogno grajenju produceur, ki imajo vgrajene druge procedue iz poglavja 1.1.8)

Skratka programe hocemo graditi tako, da uporabljajo podatke na nacin, da nimajo nobenih predpostavk o tem, kaksni naj so ti podatki (oziroma cim manj), ravno dovolj za izvajanje potrebnih operacij. Hkrati so konkretne reprezentacije podatkov definirane neodvisno od programov, ki podatke uporabljajo.

Selektorji in konstruktorji.

Aritmeticne operacije z racionalnimi stevili

  (define (add-rat x y)
    (make-rat (+ (* (numer x) (denom y))
                 (* (numer y) (denom x))
                 )
              (* (denom x) (denom y))
              )
    )

  (define (sub-rat x y)
    (make-rat (- (numer x) (denom y)
                 (numer y) (denom x)
                 )
              (* (denom x) (denom y))
              )
    )

  (define (mul-rat x y)
    (make-rat (* (numer x) (numer y))
              (* (denom x) (denom y))
              )
    )

  (define (div-rat x y)
    (make-rat (* (numer x) (denom y))
              (* (denom x) (numer y)))
    )

  (define (equal-rat? x y)
    (= (* (numer x) (denom y))
       (* (numer y) (denom x))
       )
    )

  (define (make-rat n d) (cons n d))

  (define (numer x) (car x))
  (define (denom x) (cdr x))

  (define (print-rat x)
    (display (numer x))
    (display "/")
    (display (denom x))
    (newline)
    )
  (define one-half (make-rat 1 2))
  (define one-third (make-rat 1 3))

  ;; excercise 2.1

  (define (make-rat-norm n d)
    (if (< d 0)
        (make-rat (* n -1) (* d -1))
        (make-rat n d)
        )
    )

Sestavljena strkutura par, ki je konstruirana s primitivno proceduro cons. S primitivnimi procedurami car in cdr lahko dobimo prvi in ostale elemente para.

Predstavljanje racionalnih stevil

Glej zgornji codeblock.

Pregrade abstrakcij

Splosna ideja podatkovnih abstrakcij je, da se identificira za vsak tip podatka osnovni set opraracij, s katerimi bodo vse procedure, ki bodo manipulirale podatke operirale, oziroma bodo iz njih sestavljene. Nato se uporabljamo samo te operacije pri delu s podatki.

Pregrade:

  • programi, ki uporabljajo racionalna stevila
  • racionalna stevila v problemskem polju

    • add-rat, sub-rat
  • racionalna stevila kot stevci in imenovalci

    • make-rat, number, denom
  • racionalna stevila kot pari

    • cons, car, cdr
  • kakor so pac pari implementirani

Procedure na vsakem nivoju so vmesniki, ki definirajo abstrakcijski nivo in med sabo povezujejo razlicne nivoje.

Ena od prednosti razdelitve na nivoje je, da je programe lazje vzdrzevati in spreminjati, ker lahko delas spremembe na posameznem nivoju, ki ne vplivajo izven svojega nivoja.

vaja 2.2

  ;; crte v prostoru
  (define (make-segment startp endp)
    (cons startp endp)
    )
  (define (make-line x1 y1 x2 y2)
    (make-segment (make-point x1 y1) (make-point x2 y2))
    )

  (define (start-segment segment)
    (car segment)
    )
  (define (end-segment segment)
    (cdr segment)
    )


  (define (make-point x y)
    (cons x y)
    )
  (define (x-point p) (car p))
  (define (y-point p) (cdr p))

  (define (mid-point segment)
    (make-point
     (/ (+ (x-point (start-segment segment)) (x-point (end-segment segment))) 2)
     (/ (+ (y-point (start-segment segment)) (y-point (end-segment segment))) 2)
     )
    )

  (define (print-point p)
    (display "(")
    (display (x-point p))
    (display ",")
    (display (y-point p))
    (display ")")
    (newline)
    )

  ;; vaja 2.3 :: segment je lahko tudi pravokotnik, ce nimamo rotacije in je crta
  ;; vedno diagonala. Delal bom brez rotacije, zato ker potem ni dovolj imeti
  ;; konstruktorja, ki je samo kons, ampak rabim 3 parametre, segment in rotacija
  ;; in potem nvm kako delat selektorje in pa se vse se mi zakomplicira in se mi
  ;; ne da, ker je nedelja zvecer.

  ;; brez rotacije - 2a + 2b
  (define (perimeter rectangle)
    (+ (* 2 (side-a rectangle)) (* 2 (side-b rectangle)))
    )
  (define (area rectangle)
    (* (side-a rectangle) (side-b rectangle))
    )
  ;; selektor (brez rotacije)
  (define (side-a rectangle)
    (abs (- (x-point (start-segment rectangle)) (x-point (end-segment rectangle))))
    )
  (define (side-b rectangle)
    (abs (- (y-point (start-segment rectangle)) (y-point (end-segment rectangle))))
    )
  ;; sedaj vpeljemo drugo reprezentacijo pravokotnikov (nic vec s segmentom)
  ;; ali lahko obseg in ploscina se vedno delujeta?

  ;; odvisna sta od side-a in side-b. Ce to zemanjam, bosta obseg in ploscina se
  ;; vedno delovali.

Kaj so podatki?

Pri racionalnih stevilih imamo se en pogoj:

(/ (numer x) (denom x)) = n/d

Selektorji konstruktorji in pogoji tvorijo veljavno reprezentacijo.

Vsaka trojica procedur, ki ustreza pogoju, da ce zdruzis dva objekta, in potem z eno proceduro dobis iz zdruzenih prvi objekt in z drugo drugi objekt, je potem trojica procedu za delanje s pari.

Trojico procedur (cons, car, cdr) se da implementirati brez podatkov:

  (define (cons-p x y)
    (define (dispatch m)
      (cond
       ((= m 0) x)
       ((= m 1) y)
       (else (error "Argument not 0 or 1 -- CONS" m))
       )
      )
    dispatch)
  (define (car-p z) (z 0))
  (define (cdr-p z) (z 1))

Zdaj imamo procedure za delanje s pari, ki so definirane brez podatkov. Obskurno, ampak v okviru definije delanja s pari. Na tem primeru vidimo, da zmoznost manipuliranja procedur kot objektov avtomaticno omogoci moznost za reprezentacijo sestavljenih podatkov. (Proceduralna reprezentacija podatkov bo igrala osrednjo vlogo v nadaljevanju - temu se rece message passing in bo osnovno orodje v tretjem poglavju o problemih modeliranja in simulacije).