360 likes | 512 Views
Functies als Getallen. Jan Martin Jansen. Functies als Getallen. Vraag Hoeveel formalisme is er nodig om een serieuze programmeertaal te maken? Serieuze Programmeertaal Het moet mogelijk zijn om een algoritme te schrijven om het n-de priemgetal te berekenen voor willekeurige n.
E N D
Functies als Getallen Jan Martin Jansen
Functies als Getallen Vraag Hoeveel formalisme is er nodig om een serieuze programmeertaal te maken? Serieuze Programmeertaal Het moet mogelijk zijn om een algoritme te schrijven om het n-de priemgetal te berekenen voor willekeurige n. Het moet mogelijk zijn in deze taal een interpreter voor de taal zelf te schrijven.
Functies als Getallen Antwoord Het blijkt dat je met erg weinig formalisme toe kan! Definitie De taal bestaat uit uitsluitend functies. De enige operatie is functietoepassing. Er zijn geen voorgedefiniëerde datatypes.
Syntax Informeel m.b.v. voorbeelden Functiedefinities: f a b = b a app f x = f x id x = x k x y = x s f g x = f x (g x) fix f x = f (fix f) x
Semantiek Berekenen van een expressie Argumenten invullen in rechterkant: k f s f f k id id k k app k f k f
Anonieme functies Het blijkt handig te zijn ook anonieme functies toe te staan: app (x -> x) f (x -> x) f f (x -> y -> k y x) f s k s f s Anonieme functies zijn niet echt nodig, maar zijn zeer nuttig voor de leesbaarheid van programma’s.
Grammatica Syntax Formeel d.m.v. grammatica function ::= iden {iden} ‘=’ expr expr ::= iden ‘->’ expr | application application ::= factor {factor} factor ::= iden | ‘(’ expr ‘)’
Semantiek Formeel m.b.v. interpreter Informeel evalueren van een expressie (= functie applicatie): herhaal (zolang de expresie functieapplicaties bevat met voldoende argumenten) vervang de functieaanroep door de rechterkant van de functie met de argumenten ingevuld
Serieuze programma’s Getallen en bewerkingen Wat is een natuurlijk getal? Ieder getal een eigen functie: zero x = x one x = x two x = x .... Probleem Er zijn oneindig veel getallen!
Getallen Oplossing Recursieve definitie Een getal is of nul of opvolger van een ander getal: zero f g = f suc n f g = g n de functie zero stelt dus het getal 0 voor en selecteert van twee argumenten het eerste de functie suc n heeft ook twee argumenten en past het tweede argument toe op n de functie suc (suc (suc zero)) stelt het getal 3 voor.
Operaties op Getallen Daar de definitie van een getal recursief is moeten ook de operaties op getallen recursief gedefinieerd worden. Let ook op het gebruik van anonieme functies.
Optellen add n m = m n (mm1 -> suc (add n mm1)) Recursie over m m zero geeft n m = suc mm1 ==> Resultaat = suc (add n mm1) n + m optellen is dus niets anders dan m keer 1 bij n optellen!
Voorbeelden • add (suc zero) zero • zero (suc zero) ... • suc zero • add (suc zero) (suc zero) • suc zero (suc zero) • (mm1 -> suc (add (suc zero) mm1)) • (mm1-> suc (add (suc zero) mm1)) zero • suc (add (suc zero) zero) • suc (suc zero) • 1 + 1 is dus inderdaad 2!
Vermenigvuldiging mult n m = m zero (mm1 -> add n (mult n mm1))) Vermenigvuldiging is dus niets anders dan herhaald optellen.
Aftrekken Eerst definiëren we de predecessor functie. Deze is alleen gedefinieerd voor getallen ongelijk 0. error x = x pred n = n error (nm1 -> nm1) error wordt gebruikt om een fout aan te geven. sub n m = m n (mm1 -> pred (sub n mm1)) m van n aftrekken is dus m keer 1 van n aftrekken.
Andere operaties If-then-else true x y = x false x y = y ifte b x y = b x y true selecteert van 2 argumenten het eerste en false het tweede. ifte past de functie b (true of false) toe op de twee argumenten x en y (eigenlijk niet nodig).
Andere operaties (On)gelijkheids testen. eq n m = n (m true (mm1 -> false)) (nm1 -> (m false (mm1 -> (eq nm1 mm1)))) gt n m = n (m false (mm1 -> false)) (nm1 -> (m true (mm1 -> (gt nm1 mm1))))
Andere operaties Div en Mod mod n m = ifte (gt m n) n (mod (sub n m) m) div n m = ifte (gt m n) zero (suc (div (sub n m) m)) Voorbeeld: Faculteit fac n = n (suc zero) (nm1 -> mult n (fac nm1))
Priemgetallen Om priemgetallen te vinden gebruiken we het beroemde zeef-algoritme van Erasthostenes. Om dit in ons geval te kunnen toepassen hebben we eerst een opslagstructuur voor getallen nodig.
Lijsten Ook een lijst is een recursieve structuur. Leeg of bestaat uit head en tail. empty z1 z2 = z1 cons x xs z1 z2 = z2 x xs Voorbeelden: cons zero empty cons (suc zero) (cons zero empty)
Operaties op lijsten from n = cons n (from (suc n)) take n ls= ls empty (x -> xs -> n empty (nm1 -> (cons x (take nm1 xs))))
Operaties op lijsten filter p ls = ls empty (x -> xs -> ifte (p x) (cons x (filter p xs)) (filter p xs))
Priemgetallen We zijn nu klaar voor de priemgetallen! notmodzero x y = ifte (eq (mod y x) zero) false true notmodzero : als y mod x = 0 dan false, anders true two = suc (suc zero) primes = sieve (from two)
Priemgetallen Sieve sieve ls = ls error (x -> xs -> cons x (sieve (filter (notmodzero x)xs)))
Interpreter De interpreter die we beschrijven is voor een iets eenvoudigere taal dan Yapl omdat we afzien van anonieme functies. De datastructuur voor expressies met variabelen wordt beschreven door de volgende 3 functies: app f g f1 f2 f3 = f1 f g var n f1 f2 f3 = f2 n func n f1 f2 f3 = f3 n
Interpreter app f g beschrijft de toepassing van f op g. var n beschrijft een variabele met naam n (in de praktijk is dit een getal). func n beschrijft de aanroep van functie met naam n (ook een getal).
Interpreter: Voorbeelden id x = x de body van deze functie wordt var 0 s f g x = f x (g x) de body van deze functie wordt (app (app (var 0) (var 2)) (app (var 1) (var 2)))
Interpreter • De functie eval heeft 3 argumenten: • De te evalueren expressie • Een argumenten stack (lijst) • Een lijst met functiebeschrijvingen met als items een tuple van het aantal argumenten en de body van de functie(als boven)
Tuples tup a b f = f a b fst t = t (a -> b -> a) snd t = t (a -> b -> b)
Interpreter In func n geeft n de plaats in de functielijst aan waar de functie kan worden gevonden. Bij het evalueren van een applicatie wordt rechterkant op de stack gezet waarna linkerkant wordt geëvalueerd. Bij het evalueren van een functie aanroep wordt er gecontroleerd of er voldoende argumenten op de stack staan; zo ja, dan wordt de functie body opgezocht en de argumenten worden gesubstitueerd.; zo nee, dan wordt de functie applicatie herbouwd m.b.v. rebuild.
Interpreter eval exp es fs = exp (f -> g -> eval f (cons (eval g empty fs) es) fs) (n -> error) (n -> (ge (count es) (fst (el n fs))) (eval (subst (snd (el n fs)) es) (drop (fst (el n fs)) es) fs) (rebuild (func n) es))
Interpreter subst e es = e (f -> g -> app (subst f es) (subst g es)) (n -> el n es) (n -> func n) rebuild e es = es e (x -> xs -> rebuild (app e x) xs)
Interpreter Voorbeeld Implementatie van getallen definitie van zero, suc, add en hadd tup 2 (var 0) moet gelezen worden als f a b = a 2 is het aantal argumenten en var 0 is de body van de functie Voor add is er gebruik gemaakt van een hulpfunctie
Interpreter Voorbeeld Implementatie van getallen numfuncs = (cons (tup 2 (var 0)) (cons (tup 3 (app (var 2) (var 0))) (cons (tup 2 (app (app (var 1) (var 0)) (app (func 3) (var 0)))) (cons (tup 2 (app (func 1) (app (app (func 2) (var 0)) (var 1)))) empty))))
Interpreter Voorbeeld Implementatie van getallen Een aantal handige afkortingen: nul = func 0 een = app opv nul twee = app opv een opv = func 1 telop = func 2
Interpreter Voorbeeld Implementatie van getallen Code van 2 + 2 numvb = eval (app (app telop twee) twee) empty numfuncs