sicp/zapiski/sicp-lio.org

547 lines
14 KiB
Org Mode
Raw Normal View History

2024-04-30 19:18:58 +02:00
#+TITLE: Structure and Interpretation of Computer Programs
#+AUTHOR: Lio Novelli
* Foreword and Preface
#+begin_quote
Lisp je preživeli, v uporabi je že "polovico stoletja".
#+end_quote
#+begin_quote
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.
#+end_quote
#+begin_quote
Ž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.
#+end_quote
#+begin_quote
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".
#+end_quote
* 1. 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.
** meta
Linki:
https://develop.spacemacs.org/layers/+lang/scheme/README.html
https://www.nongnu.org/geiser/
https://www.gnu.org/software/guile/learn/
https://spritely.institute/static/papers/scheme-primer.html#introduction
Kako nastavit spacemacs, in malo o guile-u.
*** video lekcije
https://yewtu.be/channel/UCEBb1b_L6zDS3xTUrIALZOw (6.001 SICP: Structure and Interpretation of Computer Programs (2004))
https://yewtu.be/playlist?list=PL7BcsI5ueSNFPCEisbaoQ0kXIDX9rR5FF (MIT 6.001 Structure and Interpretation, 1986)
** 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.
#+begin_src scheme
(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)
#+end_src
#+RESULTS:
: 128
#+begin_src scheme
(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)
#+end_src
#+RESULTS:
: 128
**** pravilno
#+begin_src scheme
(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)
#+end_src
#+RESULTS:
: 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.6
[[file:sqrt-newton.scm][sqrt-newton.scm]]
*** 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
2024-05-16 22:33:30 +02:00
** 1.2.2 Drevesna rekurzija
** 1.2.3 Redi rasti
** 1.2.4 Eksponentna funkcija
2024-05-29 21:54:05 +02:00
#name: exponent
#+begin_src scheme
;; 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)))
)
)
#+end_src
2024-05-16 22:33:30 +02:00
** 1.2.5 Najvecji skupni deljitel
** 1.2.6 Primer: Iskanje prastevil
** 1.3 Sestavljanje abstrakcij s procedurami visjega reda
2024-05-29 21:54:05 +02:00
Procedure, ki spreminjajo druge procedure se imenujejo *procedure višjega reda*.
2024-05-16 22:33:30 +02:00
** 1.3.1 Procedure kot argumenti
2024-05-29 21:54:05 +02:00
Primer vsote.
#+begin_src guile
#+end_src
2024-05-16 22:33:30 +02:00
//exercise 1.29
#name: simpson
#+begin_src scheme
(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)
)
#+end_src
#+RESULTS:
| 3.7499625000000045 | 3.7499996249995324 | 3.644925346666673 | 3.7499999999995324 | 3.749893334961112 |
// exercise 1.30
#+begin_src scheme
(define (sum-i term a next b)
(define (iter a result)
(if (> a b)
result
(iter (next a) (+ result (term a)))
)
)
(iter a 0)
)
#+end_src
2024-05-29 21:54:05 +02:00
// excercise 1.30, 1.31. 1.32
#+begin_src scheme
;; 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)
)
2024-05-29 21:54:05 +02:00
#+end_src
** 1.3.2 Sestavljanje procedur z ~Lambda~
Splosna forma ~let~ izraza
#+begin_example scheme
(let ((<var1> <exp1>)
(<var2> <exp2>)
...
(<varn> <expn>))
<body>)
#+end_example
To je okrajsava za
#+begin_example scheme
((lambda (<var1> ... <varn>)
<body>)
<exp1>
...
<exp2>
)
#+end_example
2024-05-16 22:33:30 +02:00
** 1.3.3 Procedure kot splosne metode
2024-05-29 21:54:05 +02:00
Ce pogledamo proceduro za integral, vidimo mocnejse abstrakcije: procedure, ki
izrazajo splosne racunske metode, neodvisne od posameznih vkljucenih funkcij.
2024-05-16 22:33:30 +02:00
** 1.3.4 Procedure kot vrnjene vrednosti
2024-05-29 21:54:05 +02:00
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).