Föreläsning 9 i programmeringsparadigm. 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 databasen 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 39. Detta är det sätt jag rekomenderar. I Brna beskrivs hur man kör Prolog i eget fönster. Labben behöver ej redovisas, första Prologlabben som skall redovisa är labben Släktlaborationer. 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 definitionsmä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: ihop ::(A, B)->Bool (a, b) (a, c) (x, y) True False Kan (lite klumpigt) skrivs i Haskell som (par istället för "curring") ihop(a, b) = True ihop(a, c) = True ihop _ = False Skrivs i Prolog i en "databas" som ihop(a, b). ihop(a, c). Dvs vi skriver bara Endast en pil från varje det som är sant. värde Logikprogrammering är programmering med predikat. Prolog skriver vi i regel endast ut det som anses sant. Allt som inte är sant är nog osant. Eftersom ihop(a, b) = True och ihop(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 och d av typen B. Vi kan också rita detta som a ihop :: 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 ihop(a, b). ihop(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 predikatnamnet ihop Märk att vi struntar i osanna saker, dvs eftersom ihop(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) svar ( True enl databasen) {source_info} fråga (query)?- happy(r). med logisk variabel R = jane? {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" och 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. Prologpredikat motsvarar således "uncurried" predikat i Haskell (funktioner med typen (...,...,... )->Bool. Hur skriver vi Prologpredikat motsvarande funktioner med annan resultattyp än Bool? Jo så här: d(x, 3). d x = 3 Predikatet d säger att det är sant att X och 3 "hänger ihop" ("är ihop" är moderna svenska tror jag), dvs de har en relation. Paranteser kring "argumenten", komma mellan "argumenten", inget mellanrum före (. Brna skiljer inte på parametrar (kallas ju ibland formell parameter) och argument (aktuell parameter). f(x, Z) :- g(x, Y), h(y, Z). Funktionen d har en parameter x och ett resultat som alltid är 3. Vid ett anrop med ett visst argument binds argumentet till parameternamnet. 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. Logiskt och skrivs med, (komma) i Prolog I Haskell lat evaluering, dvs Haskell börjar Logiskt och skrivs med && i Haskell. 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" (utan värden) logiska värden. Syfte med körningar av Prolog-porgram är i regel att det logiska variablerna så småningom skall få värden genom unifieringar, dvs Prolog drar de slutsatser som krävs för att predikaten skall kunna bli sanna. "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)) mytree = 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 Eftersom reultattypen är -> Bool blir det predikat även i Haskell. I haskell skulle vi ju ocskå kunnat ha typen intree :: Eq a => (a, Tree a) -> Bool, likhet n mellan definitonerna i de två språken skullt då blivit ännu större. Märk att logiskt eller skriv normalt som ytterligare ett predikat. Logiskt och && skrivs som, (komma). Körningar på trädet ovan sidan :?- intree(5, node(n...). Main> intree 5 mytree True?- intree(6, node(n...). Main> intree 6 mytree no False Men om vi vill ha andra rsultat en Yes eller no? Hur gör vi då? Jo, Prolog går att köra "baklänges":?- intree(z, node(...). -- Ingen motsvarighet i Haskell -- Vi måste skriva en ny funktion Z = 14? ; Z = 5? ; Z = 3? ; no %betyder inga fler lösningar Prolog föreslår värden på logiska variabler för att predikatet i frågan skall bli sann. Vill vi ha fler förslag svarar vi med ;, annars vagnretur. 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)))? %inget ;
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. Om vi vill ha andra resultat än eller no i Prolog använder vi logiska variabler (stor bokstav) i frågor och mål. Exempelvis om vi vill hitta största värdet i binära träd: Prolog: Haskell: maxintree :: Ord a => Tree a->a maxintree(leaf(m), M). maxintree (Leaf zz) = zz maxintree(node(l, R), Lv) :- maxintree (Node l r) = maxintree(l, Lv), maxintree(r, Rv), Rv=<Lv. max (maxintree l) (maxintree r) maxintree(node(l, R), Rv) :- maxintree(l, Lv), maxintree(r, Rv), Lv<Rv. Körningar:?-maxInTree(node(node(leaf(3),leaf(5)),leaf(14)),Max) Main> maxintree mytree Max = 14? 14 För att definiera mytree i Prolog kan vi göra så här: mytree(node(node(leaf(3), leaf(5)), leaf(14)). eller på en rad : mytree(node(node(leaf(3),leaf(5)),leaf(14)). och köra så här:?- mytree(t), maxintree(t, M). M = 14, T = node(node(leaf(3),leaf(5)),leaf(14))? Om vi definierar max(i,j,j)/3 i Prolog så här: max finns i Haskells prelude max :: Ord a => a -> a -> a max(i,j,j) :- I =< J. max i j max(i,j,i) :- J < I. i <= j = j otherwise = i Kan vi definiera maxintree/2 så här: maxintree(leaf(m), M). maxintree(node(l, R), M) :- maxintree(l, Lv), maxintree(r, Rv), max(lv, Rv, M).
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) eller data List1 a = Null1 Ingen deklaration av "typen" behövs! Cons1 (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))) eller Cons1(3, Cons1(5, Cons1(7, Null1))) 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". Brnas bok. Funktionell programmering och logikprogrammering kallas ibland för deklarativ programmering. Jag har i denna föreläsning delvis pratat om likheter och skillnader mellan dessa paradigmer, exemplifierat med skillnader mellan Haskell och Prolog.Kapitel 1, 2 och 3 i Brnas motsvarar delvis vad vi hittills gjort, men Brna anknyter istället till logik. Dessutom har vi sagt en del om listor, avsnitt 4.3, och om sammansatta termer som behandlas väldigt lite i Brna. Brnas AND/OR -träd och "Resuolution Tables" är jag inte så förtjusta i, så det tänker jag inte tala om. Bra övningar med svar i boken.