Föreläsning 8. Typer och programmeringsstil i Haskell. När vi definerade ett schack gjorde vi så här: newtype Chess = Chess [(Square, Chessman)] -- data ist f newtype OK deriving Show -- newtype effektivare -- data istället för newtype krävs om >1 konstruerare. dvs ett schack är en lista med par av rutor och pjäser på denna ruta. Men listan föregås av konstrueraren Chess för att vi skall få typen Chess och inte typen [(Square, Chessman)]. Vi "stoppar in" listan i en "påse" med konstueraren Chess. När vi vill jobba med listan måste vi därför "plocka ut" listan, detta kan man i regel göra med mönstermatchning. Vill vi så göra ett nytt schack måste vi "stoppa in" den nya listan i påse genom att använda konstueraren Chess. T ex newboard:: [(Square, Chessman)] -> Chess -> Chess newboard ((sq, cm):csqcms) (Chess b) = newboard csqcms (Chess (update sq cm b)) newboard [] b = b Funktionen update jobbar nu med en lista : update :: Square -> Chessman -> [(Square, Chessman)] -> [(Square, Chessman)] Denna funktion kan man nu jobba med som man är van vi, t ex använda map. Kan man inte helt enkelt "bara komma ihåg" att ett schack är en lista [(Square, Chessman)], dvs låta bli att definiera en datatyp? Jo det går och är på sätt och vi enklare. newboard1 skulle då kunna se ut så här: newboard1:: [(Square, Chessman)] -> [(Square, Chessman)] -> [(Square, Chessman)] newboard1 ((sq, cm):csqcms) b = newboard1 csqcms (update sq cm b) newboard1 [] b = b Man får nu lite mindre hjälp av typ-systemet när man programmerar, och programmet blir (kanske?) lite mer svårläst. Ett mellanting får man genom att göra type Chess1 = [(Square, Chessman)] --typsynonym newboard1 fungerar nu på samma sätt som förut men man kan om man vill ange dess typ som newboard1:: [(Square, Chessman)] -> Chess1 -> Chess1
Paradigmöversikt, paradigmhistoria, paradigmgeografi. Se även föreläsning 1. Användning av Prolog. Prolog har framför allt används inom AI ( Articifial Intellegence), och speciellt i Europa. I USA har lisp (som är ett tidigt funktionellt språk) används inom AI. Komplettering Laboration Att komma igång med Prolog. På /info/progp02/prolog/happypersons.pl finns datbasen på Brna sid 21. Använd den för att göra några körningar. Hur man kör Prolog (sicstus) under emacs, se labhäftet sid 51. Detta är det sätt jag rekomenderar. I Brna beskrivs hur man kör Prolog i eget fönster. sicstus är ett svenskt Prolog-paket från Stockholm Intitute of Computer Science i Kista.
Funktioner, predikat och relationer. Funktioner kan ses som avbildningar, "mappningar", från värden i en definitonsmängd (domän) till värden i en värdemängd (range) : f :: A -> B a d b s t Endast en pil från varje värde Funktioner som har som värdemängd värdena True och False i typen Bool kallas predikat: (a, b) (a, c) (x, y) p :: (A, B) -> Bool True False Skrivs i Prolog i en "databas" som hängerihop(a, b). hängerihop(a, c). Dvs vi skriver bara det som är sant. Endast en pil från varje värde Prolog skriver vi i regel endast ut det som anses sant. Allt som inte är sant är nog osant. Eftersom p (a, b) = True och p (a, c) = True (se bilden i ovan) kan man se predikat som att värdet a av typen A "hänger ihop med" både värden c oh d av typen B. Vi kan rita detta som a hängerihop :: A -> B Flera pilar från varje värde. hängerihop är därför ej en funktion, utan en relation. b c Skrivs i Prolog i en "databas" som hängerihop(a, b). hängerihop(a, c). Betyder att det är sant att a hänger ihop med b och att det är sant att a hänger ihop med c. Brna: De två raderna ovan är ett predikat med två satser (clause) och predeikatnamnet hängerihop Märk att vi struntar i osanna saker, dvs eftersom p(x, y) = False så skrivs inte detta i "databasen". Värden kallas i Prolog konstanter. Konstanter är atomer eller tal (numbers). I denna kurs kommer vi mest att syssla med atomer, som är namn som börjar med små bokstäver.
Körning av sictus i emacs. Terminolgi mm. en sats, ett faktum fyra satser (clause), alla fakta sats (clause), fakta mål (goal) predikatnamn argument (en konstant, en atom) wise(jane). woman(jean). woman(jane). woman(joan). woman(pat). I det övre emacsfönstret skriver vi en databas med predikat, vad vi anser vara sant. två satser, fakta tre satser, fakta wealthy(jane). wealthy(jim). healthy(jim). healthy(jane). healthy(jean). Detta är databasen på sid 21 i Brna. Finns på /info/progp02/prolog/happypersons.pl. Lek med denna databas i första labben. tre satser, regler (rules, non-unit clauses) happy(p) :- healthy(p), woman(p). happy(p) :- wealthy(p), woman(p). happy(p) :- wise(p), woman(p). eller (disjunction) uttrycks i Prolog med flera satser för samma prdikat mål huvud (head) <= if mål implikation mål och conjunction kropp (body) regel (rule, non-unit clause)?- woman(jane). fråga (query) yes svar ( True enl databasen) {source_info} fråga (query)?- happy(r). med logisk variabel R = jane? yes {source_info}?- förslag till värde på R svar (med värdet enligt förslaget sant, dvs happy(jane) = True promt för ny fråga (query) argument (en logisk variabel) I det nedre emacsfönstret kan vi fråga databasen och få svar. emacsfönstret för minibufferten
Prolog och logisk programmering (och andra paradigm). Vad? (logisk, funktionell läsning) Hur?(procedurell läsning) Logik prog. Hels databasen kan läsas som logika utsagor. Prolog svarar alltid, korrekt och fort Hels databasen kan läsas som logika utsagor (deklarativ läsning) men har även en procedurell läsning Prolog svarar alltid, korrekt. Procdurella läsningen beskriver hur och hur fort. Databasen innehåller "fusk", saker som endast har procedurell läsning. "fusket" gör bl a att Prolog svarar snabbare ibland och kan göra det som vanliga programspråk kan. Pardiso Prolog Pure Prolog Non-pure Prolog Ofta realiteten Funk. prog. Haskell kan läsas som definitioner av typer och värden ("vanliga värden" funktioner) Hugs beräknar uttryck Hugs beräknar uttryck p ss som i matte. Typer kollas, viktigt att rekursion slutar i basfall och att det finns ekvationer för alla fall Pardiso Haskell Haskell körning Imperativ OO Java-program (metoderna) har mest en procedurell läsning Java-tolken kör metoderna Java
Procedurer i Prolog och funktioner i Haskell. d(x, 3). d x = 3 Predikatet d säger att det är sant att X och 3 Funktionen d har en parameter x "hänger ihop" ("är ihop" är moderna svenska tror jag), och ett resultat som alltid är 3. Vid dvs de har en relation. OBS! Paranteser om "argumenten", ett anrop med ett visst argument komma mellan "argumenten", inget mellanrum före (. binds argumentet till parameternamnet. Brna skiljer inte på parametrar (kallas ju ibland formell parameter) och argument (aktuell parameter). f(x, Z) :- g(x, Y), h(y, Z). f x = h ( g x) -- x känt Predikatet f säger att det är sant att X och Z Funktionen f har en parameter x "hänger ihop", dvs de har en relation, och ett resultat som är funktionen h om g(x, Y) och h(y, Z) är sant. applicerad på resultatet av funktionen g Procedeurellt försöker Prolog först med applicerad på ett visst argument g(x, Y) sedan med h(y, Z). bundet till parameternamnet x. I Haskell lat evaluering, dvs Haskell börjar med h men tvingas normalt först göra g. Datastrukturer i Prolog och Haskell. Hittills har alla värden, tex pat, jean, jim, varit namn med gemen initial. Dessa namn är atomer, de vanligaste formen av konstanter, i Prolog. De exakta reglerna för konstanter står finstilt på sidan 7 i avsnitt 2.4, och också i 10.1 sid 96.. Atomer har vi bara använt utan vidare utan föregående deklaration, atomer har normalt liten initial, dvs tvärtom mot Haskell. I Haskell måste alla namn man använder vara deklarerade och ha värden (vara definierade), om inte annat i Preluden, dvs Haskell-namn har värden från "början". I prolog kan logiska variabler under körningen vara helt utan värden eller ha värden som delvis är andra värdelösa logiska värden. Syfte med körningar av Prolog-porgram är ju i regel att det logiska variablerna så småningom skall få värden genom unifieringar. "Atom deklaration" i Haskell: data Atom = Pat Jean Jim,... -- Versal initial både i Typnam, och i Konstruerare. Prolog har en mycket lättsinning syn på typ-deklarationer, de behövs helt enkelt inte. Bekvämt (roligt?) men farligt.
Hur gör man lite mer sammansatta värden i Prolog? Jo, så här gör man träd i Prolog och Haskell: data Tree a = Leaf a Ingen deklaration av "typen" behövs! Node (Tree a) (Tree a) node node leaf 14 Trädet leaf 3 leaf 5 skrivit i Prolog respektive Haskell: node(node(leaf(3),leaf(5)), leaf(14)) Node (Node (Leaf 3) (Leaf 5)) (Leaf 14) En funktion intree som kollar att ett värde finns i ett binärt träd: intree :: Eq a => a -> Tree a -> Bool intree(z, leaf(z)). intree z (Leaf zz) = z == zz intree(z, node(r, L)) :- intree(z, L). intree(z, node(r, L)) :- intree(z, R). intree z (Node l r) = intree z l intree z r Körningar på trädet påförra sidan :?- intree(5, node(n...). yes Main> intree 5 (Node... ) True yes?- intree(6, node(n...). Main> intree 6 (Node (Node (Leaf 3) (L no False Prolog går att köra "baklänges":?- intree(z, node(...). -- Ingen motsvarighet i Haskell Z = 14? ; Z = 5? ; Z = 3? ; no Till och med så här kan man köra i Prolog:?- intree(12, R). R = leaf(12)? ; R = node(_a,leaf(12))? ; R = node(_a,node(_b,leaf(12)))? yes
Värden som tex node(leaf(3),leaf(5)), kallas är sammansatta termer (compound term). De behandlas nästan inte alls i Brna, se dock avsnitt 10.3 på sid 97. Även konstanter, t ex atomer, och logiska variabler är termer. Syntaxmässigt används termer både som värden och när man skriver mål i huvud och kropp i klausulerna i ett predikat. Dvs "kod" och "data" ser likadana ut! Detta är ingen slump, i avancerad Prolog kan "data" framställd under körningen tillfogas databasen ("programmet") under körningen ("live")! Roligt (?) men farligt, farligt. Listor i Prolog och Haskell (Exempel i Brna Chapter 4.3). Både i Prolog och Haskell kan man själv bygga upp listor med de konstruktioner som finns i språken på samma sätt som vi byggde binära träd ovan, listor är ju "uniära" träd: data List a = Null Ingen deklaration av "typen" behövs! Cons a (List a) Listan 3, 5, 7 skrivs i Prolog respektive Haskell: cons( 3, cons( 5, cons (7, null) Cons 3 ( Cons 5 (Cons 7 Null))) Men både i Prolog och Haskell finns inbyggd speciell (men olik) syntax för listor: [3 5 7 [] ] 3:5:7 : [] dvs man skriver i stället för :. Man måste dock i Prolog omge allt med [] (Ologiskt?). I Haskell måste man ofta omge mönster med : med vanliga paranteser () pga presedensreglerna. Listan 3, 5, 7 skrivs i Prolog respektive Haskell enklast som [3, 5, 7 ] [3, 5, 7 ] I Prolog behöver inte elementen vara av samma typ. Några fall av mönsterpassning (pattern match, "unifiering") i Prolog och Haskell : [X Y] = [ f, r, e, d] Result_Wanted = [b X] let (x:y) = [ F, R, E, D] in let resultwanted = (B : x) in X = [A, B, C Y] let (a:b:c: y) = x in -- y söks Som vanligt är reglerna för stora och små initialer "tvärt om".