PPI - Teil 1 - Haskell - Dokumentation Michael Baron, Nils Oberhauser 11. Mai 2005 1 Phasen der Entwicklung 1.1 Erstes Treffen (Do, 14. April 2005) • Vorbesprechung für das Praktikum Praktische Informatik. • Austeilung der Praktikumsanleitung. • Grobe Gruppeneinteilung. 1.2 Der Anfang (Do, 21. April 2005) • Einteilung in Untergruppen • Übernahme des Parsers für arithmetische Ausdrücke (TaschenrechnerBsp.) (10:00-12:00) (Source siehe Praktikumsanleitung) • Hinzufügen einer Funktion zum Ausrechnen arithmetischer Syntaxbäume calculate (12:00-13:00) (Source calculator.y), welche mittels PatternMatching realisiert wurde. calculate :: Expr -> Int calculate (Plus (Number n1) (Number n2)) = n1 calculate (Minus (Number n1) (Number n2)) = n1 calculate (Times (Number n1) (Number n2)) = n1 -- calculate (Div (Number n1) (Number n2)) = + n2 - n2 * n2 n1 / n2 calculate (Plus e1 (Number n2)) = (calculate e1) + n2 calculate (Minus e1 (Number n2)) = (calculate e1) - n2 calculate (Times e1 (Number n2)) = (calculate e1) * n2 1 -- calculate (Div e1 (Number n2)) = (calculate e1) / n2 calculate (Plus (Number n1) e2) = n1 calculate (Minus (Number n1) e2) = n1 calculate (Times (Number n1) e2) = n1 -- calculate (Div (Number n1) e2) = + (calculate e2) - (calculate e2) * (calculate e2) n1 / (calculate e2) Auskommentierung der Division, da Typunverträglichkeit. Drei verschiedene Versionen für Basiswerte und Mischungen aus Basiswert und Unterausdruck. Beispiel-Aufruf *Calc> calculate (parser (lexer "(1+2)*3")) 9 Aber zum Beispiel keine Prioritäten-Definition der einzelnen Operatoren. So liefert folgende Rechnung ein falsches Ergebnis: *Calc> calculate (parser (lexer "1+2*3")) 9 1.3 Rechner für konstante Bool’sche Ausdrücke (So, 24. April 2005) • Anpassung des Taschenrechners an konstante Bool’sche Ausdrücke. (16:30-18:00) (Source ProofEngine-mib.y) • Neudefinition der einzelnen Token (logische Einheiten einer Formel) %name parser %tokentype { Token } %token Bool { TokenBool $$ } ’&’ { TokenAnd } ’|’ { TokenOr } ’=>’ { TokenImpl } ’<=>’ { TokenEqui } ’-’ { TokenNot } ’(’ { TokenOB } ’)’ { TokenCB } • Einführung einer (letztlich falschen) Rangfolge logischer Operatoren 2 %nonassoc %right %left %nonassoc %% ’<=>’ ’=>’ ’&’ ’|’ ’-’ • Übersetzung der Backus-Naur-Form eines logischen Ausdrucks in HappyParser-Deklaration. Formula :: { Formula } Formula : Formula ’&’ Formula | Formula ’|’ Formula | Formula ’=>’ Formula | Formula ’<=>’ Formula | ’-’ Formula | ’(’ Formula ’)’ | Bool { { { { { { { And Or Impl Equi Not $1 $1 $1 $1 $2 $2 Bool $1 $3 $3 $3 $3 } } } } } } } • Einführung der Haskell-Datentypen Token und Formula. (siehe Quellcode) • Anpassung des Lexers -- Lexer lexer :: String -> [Token] lexer lexer lexer lexer lexer lexer lexer lexer lexer lexer [] = [] (’&’:xs) = TokenAnd (’|’:xs) = TokenOr (’=’:(’>’:xs)) = TokenImpl (’<’:(’=’:(’>’:xs))) = TokenEqui (’-’:xs) = TokenNot (’T’:xs) = TokenBool True (’F’:xs) = TokenBool False (’(’:xs) = TokenOB (’)’:xs) = TokenCB lexer (x:xs) | isSpace x = lexer xs | otherwise = error "parse error" 3 : : : : : : : : : (lexer (lexer (lexer (lexer (lexer (lexer (lexer (lexer (lexer xs) xs) xs) xs) xs) xs) xs) xs) xs) • Deklaration extensionaler logischer Aussageoperationen (Implikation, Äquivalenz). -- Implikation, Aequivalenz impl :: Bool -> Bool -> Bool equi :: Bool -> Bool -> Bool impl impl impl impl False False True True False True True False = = = = True True True False equi equi equi equi False False True True False True True False = = = = True False True False • und zu guter Letzt: Defintion einer calc-Funktion für logische Syntaxbäume mittels Pattern-Matching. calc :: Formula -> Bool calc calc calc calc (And (And (And (And (Bool ( (Bool ( b1) f1) b1) f1) (Bool (Bool ( ( b2)) b2)) f2)) f2)) = = = = b1 && (calc f1) && b1 && (calc f1) && b2 b2 (calc f2) (calc f2) calc calc calc calc (Or (Or (Or (Or (Bool ( (Bool ( b1) f1) b1) f1) (Bool (Bool ( ( b2)) b2)) f2)) f2)) = = = = b1 || (calc f1) || b1 || (calc f1) || b2 b2 (calc f2) (calc f2) calc calc calc calc (Impl (Impl (Impl (Impl (Bool ( (Bool ( b1) f1) b1) f1) (Bool (Bool ( ( b2)) b2)) f2)) f2)) = = = = impl impl impl impl b1 (calc f1) b1 (calc f1) b2 b2 (calc f2) (calc f2) calc calc calc calc (Equi (Equi (Equi (Equi (Bool ( (Bool ( b1) f1) b1) f1) (Bool (Bool ( ( b2)) b2)) f2)) f2)) = = = = equi equi equi equi b1 (calc f1) b1 (calc f1) b2 b2 (calc f2) (calc f2) 4 calc (Not calc (Not (Bool ( b1)) = not b1 f1)) = not (calc f1) calculate f = (calc (parser (lexer f))) Aufrufbeispiel *ProofEngine> calculate "T|F=>F&-F" False Da die Operatorenrangfolge falsch ist, muss dieses Ergebnis natürlich auch nicht stimmen. (T steht für Wahr, F für Falsch) 1.4 Es wird variabel (Di, 26. April 2005) • Implementierung der Variablenunterstützung (20:00-21:15) • Minimale Änderungen an Parser- und Typdeklaration zur Variablenunterstützung. (Source ProofEngine-mib2.y) • Minimale Änderung des Lexers zum Lexen von alphabetischen Zeichenketten • Kleinere Veränderung der calc-Funktion zur Unterstütztung einer AssignmentListe • Definition einer Dictionary-Funktion zum Extrahieren von VariablenWerten aus einem Assignment. value :: Assignment -> Variable -> Bool value ((x,y):zs) v | x == v = y | otherwise = value zs v 1.5 Suchen nach Variablen (Do, 28. April 2005) • Definition einer Funktion zur Extraktion von Variablen aus einer Formel. (vs bzw. später als varscan bezeichnet) 5 -- Variable Scanning vs :: Formula -> [Variable] vs vs vs vs (And (And (And (And (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] (vs f1) [b1] (vs f1) ++ ++ ++ ++ [b2] [b2] (vs f2) (vs f2) vs vs vs vs (Or (Or (Or (Or (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] (vs f1) [b1] (vs f1) ++ ++ ++ ++ [b2] [b2] (vs f2) (vs f2) vs vs vs vs (Impl (Impl (Impl (Impl (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] (vs f1) [b1] (vs f1) ++ ++ ++ ++ [b2] [b2] (vs f2) (vs f2) vs vs vs vs (Equi (Equi (Equi (Equi (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] (vs f1) [b1] (vs f1) ++ ++ ++ ++ [b2] [b2] (vs f2) (vs f2) vs vs vs (Neg (Neg (Var ( (Var b1)) = [b1] f1)) = (vs f1) b1) = [b1] • Elimination mehrfacher gleicher Einträge (einer sortierten Liste, wie sich später herausstellen sollte :-)) -- elimination of multiple list elems eliminate :: [Variable] -> [Variable] eliminate [] = [] eliminate (x:[]) = [x] eliminate (x:(y:zs)) = if x == y then (eliminate (x:zs)) else (x:(eliminate (y:zs))) • Zusammenbau einer Assignmentliste alller möglichen Assignments für eine Liste aller Variablen (so in etwa wie die Berechnung von Potenzmengen realisiert)... 6 posA :: [Variable] -> [Assignment] posA [] = [[]] posA (v:vs) = [ ((v,True):a) | a <- (posA vs) ] ++ [ ((v,False):a) | a <- (posA vs) ] • ...und die Berechnung aller zugehöriger Ausdrucks-Werte calca :: [Assignment] -> Formula -> [Bool] calca a f = [ (calc n f) | n <- a ] calct f = calca (posA (getVarsFromString f)) (parser (lexer f)) • Definition nützlicher Hilfsfunktionen (getVars bzw. getVarsFromString) -- get Vars from Formula Tree/ Formula String getVars :: Formula -> [Variable] getVars f = eliminate (sort (vs f)) getVarsFromString :: String -> [Variable] getVarsFromString s = getVars (parser (lexer s)) So, dies dauerte von 10:00-12:00 (wie immer inkl. diverser Tests) und lieferte die Datei ProofEngine-mibnio.y. (diese verwendet nun das vorgeschriebene Typenmodul TypeDefs.lhs) Beispiel-Aufruf: *ProofEngine> getVarsFromString "A=>B<=>B=>A" ["A","B"] Selbstverständlich funktioniert dies auch mit längeren Variablennamen *ProofEngine> getVarsFromString "Dies=>ist<=>ein=>Test" ["Dies","ist","ein","Test"] und nun zu posA (später buildAss), welches eben die Assignments bauen sollte *ProofEngine> posA (getVarsFromString "A=>B<=>B=>A") [[("A",True),("B",True)],[("A",True),("B",False)],[("A",False),("B",True)], [("A",False),("B",False)]] 7 1.6 und zu guter Letzt: Das Finish (So, 01. Mai 2005) • Der langersehnte Zusammenschluss aller Elementarfunktionen zu einem Ganzen plus die Definition der Schnittstellenfunktionen proofEngine (wie verlangt), isTautology, isContradiction, sowie isSatisfiable, welches allerdings nicht Bool, denn dies wäre trivial, sondern die Liste aller möglichen Satisfiabilities liefert. (Satisfiabilities erschien uns nämlich auf Grund typographischer Probleme nicht Quellcode-fähig. :-)) ------ ProofEngine.y Michael Baron, Nils Oberhauser { module ProofEngine where import TypeDefs import Char import List data Token = | | | | | | | TokenVar Variable TokenAnd TokenOr TokenImpl TokenEqui TokenNeg TokenOB TokenCB proofEngine :: String -> Result buildAss calc calcStr calcStrFilter elimMult getVars getVarsStr happyError :: :: :: :: :: :: :: :: -> -> -> -> -> -> -> -> [Variable] Assignment Assignment String [Variable] Formula String [Token] 8 [Assignment] Formula -> Bool String -> Bool Assignment -> Bool [Variable] [Variable] [Variable] a isTautology isContradiction isSatisfiable lexer lexAlpha listTrue listFalse logEqui logImpl value varscan } :: :: :: :: :: :: :: :: :: :: :: String String String String String [Bool] [Bool] Bool Bool Assignment Formula -> -> -> -> -> -> -> -> -> -> -> Bool Bool [Assignment] [Token] [Token] Bool Bool Bool -> Bool Bool -> Bool Variable -> Bool [Variable] %name parser %tokentype { Token } %token ’<=>’ { TokenEqui } ’=>’ { TokenImpl } ’&’ { TokenAnd } ’|’ { TokenOr } ’-’ { TokenNeg } ’(’ { TokenOB } ’)’ { TokenCB } Var { TokenVar $$ } %nonassoc %right %left %left %nonassoc %% ’<=>’ ’=>’ ’&’ ’|’ ’-’ Formula :: { Formula } Formula : Formula ’<=>’ | Formula ’=>’ | Formula ’&’ | Formula ’|’ | ’-’ | ’(’ | Var Formula Formula Formula Formula Formula Formula ’)’ 9 { { { { { { { Equi Impl And Or Neg Var $1 $1 $1 $1 $2 $2 $1 $3 $3 $3 $3 } } } } } } } { proofEngine s | isTautology s = Tautology | isContradiction s = Unsatisfiable | otherwise = Satisfiable (isSatisfiable s) -buildAss [] = [[]] buildAss (v:vs) = [ ((v,True):a) | a <- (buildAss vs) ] ++ [ ((v,False):a) | a <- (buildAss vs) ] -calc calc calc calc a a a a (And (And (And (And (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = (value (calc (value (calc a a a a b1) f1) b1) f1) && && && && (value (value (calc (calc a a a a b2) b2) f2) f2) calc calc calc calc a a a a (Or (Or (Or (Or (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = (value (calc (value (calc a a a a b1) f1) b1) f1) || || || || (value (value (calc (calc a a a a b2) b2) f2) f2) calc calc calc calc a a a a (Impl (Impl (Impl (Impl (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = logImpl logImpl logImpl logImpl (value (calc (value (calc a a a a b1) f1) b1) f1) (value (value (calc (calc a a a a b2) b2) f2) f2) calc calc calc calc a a a a (Equi (Equi (Equi (Equi (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = logEqui logEqui logEqui logEqui (value (calc (value (calc a a a a b1) f1) b1) f1) (value (value (calc (calc a a a a b2) b2) f2) f2) (Var ( (Var b1)) f1)) b1) calc a (Neg calc a (Neg calc a = not (value a b1) = not (calc a f1) = (value a b1) -calcStr a f = (calc a (parser (lexer f))) 10 -calcStrFilter f a = (calc a (parser (lexer f))) -elimMult [] = [] elimMult (x:[]) = [x] elimMult (x:(y:zs)) = if x == y then (elimMult (x:zs)) else (x:(elimMult (y:zs))) -getVars f = elimMult (sort (varscan f)) -getVarsStr s = getVars (parser (lexer s)) -happyError _ = error "(EE) ProofEngine :: parse error!" -isTautology s = listTrue [ (calc (ass) (parser (lexer s))) | ass <- (buildAss (getVarsStr s)) ] -isContradiction s = not (listFalse [ (calc (ass) (parser (lexer s))) | ass <- (buildAss (getVarsStr s)) ]) -isSatisfiable s = (filter (calcStrFilter s) (buildAss (getVarsStr s))) -lexer [] = [] 11 lexer lexer lexer lexer lexer lexer lexer (’<’:(’=’:(’>’:xs))) (’=’:(’>’:xs)) (’&’:xs) (’|’:xs) (’-’:xs) (’(’:xs) (’)’:xs) = = = = = = = TokenEqui TokenImpl TokenAnd TokenOr TokenNeg TokenOB TokenCB : : : : : : : (lexer (lexer (lexer (lexer (lexer (lexer (lexer lexer (x:xs) | isSpace x = lexer xs | isAlpha x = lexAlpha (x:xs) | otherwise = error "parse error" -lexAlpha (x:xs) = TokenVar alpha : (lexer s) where [(alpha,s)] = lex (x:xs) -listTrue [] = True listTrue (x:xs) = x && (listTrue xs) -listFalse [] = False listFalse (x:xs) = x || (listFalse xs) -logEqui logEqui logEqui logEqui False False True True False True True False = = = = True False True False False False True True False True True False = = = = True True True False -logImpl logImpl logImpl logImpl 12 xs) xs) xs) xs) xs) xs) xs) -value ((x,y):zs) v | x == v = y | otherwise = value zs v -varscan varscan varscan varscan (And (And (And (And (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] ++ [b2] (varscan f1) ++ [b2] [b1] ++ (varscan f2) (varscan f1) ++ (varscan f2) varscan varscan varscan varscan (Or (Or (Or (Or (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] ++ [b2] (varscan f1) ++ [b2] [b1] ++ (varscan f2) (varscan f1) ++ (varscan f2) varscan varscan varscan varscan (Impl (Impl (Impl (Impl (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] ++ [b2] (varscan f1) ++ [b2] [b1] ++ (varscan f2) (varscan f1) ++ (varscan f2) varscan varscan varscan varscan (Equi (Equi (Equi (Equi (Var ( (Var ( b1) f1) b1) f1) (Var (Var ( ( b2)) b2)) f2)) f2)) = = = = [b1] ++ [b2] (varscan f1) ++ [b2] [b1] ++ (varscan f2) (varscan f1) ++ (varscan f2) varscan varscan varscan (Neg (Neg (Var ( (Var b1)) f1)) b1) = [b1] = (varscan f1) = [b1] } Da nun aber der Ruf nach Dokumentation immer lauter wurde, dokumentierten wir mit Hilfe von Haddock einen Großteil der Funktionen (siehe ProofEngine.html) und schrieben eine PDF-Dokumentation welche noch einmal zumindest essentielle Überlegungen darstellen soll. 13 2 und nun zur Funktionsweise Vielleicht zuerst die Praxis: ... *ProofEngine> proofEngine "A=>A" Tautology *ProofEngine> proofEngine "A=>-A" Satisfiable [[("A",False)]] *ProofEngine> proofEngine "A&-A" Unsatisfiable *ProofEngine> proofEngine "A=>A&-A" Satisfiable [[("A",False)]] *ProofEngine> proofEngine "A=>A&(-A|B)" Satisfiable [[("A",True),("B",True)],[("A",False),("B",True)], [("A",False),("B",False)]] ... 14