11 · Fallstudie: Reguläre Ausdrücke 11.4 � 1 2 Die Ableitung regulärer Ausdrücke · 11.4 Die Ableitung regulärer Ausdrücke Das Wortproblem: Wir suchen eine Funktion, die den Input i gegen den regulären Ausdruck r matcht: match :: RegEx α -> [α] -> Bool match r i = i ∈ L r — Pseudo-Code! • Da L r potenziell unendlich ist, scheidet explizites Aufzählen aus. Idee Wir zerlegen das Problem: Die Eingabe i hat die Form c : cs. 1. Können Worte in L r überhaupt mit c anfangen? (Wenn nicht, sind wir fertig: i �∈ L r ⇒ match r i � False) 2. Wenn ja, welcher reguläre Ausdruck r � beschreibt die erlaubten Reste aller Worte in L r die mit c anfangen? 3. Gilt cs ∈ L r � ? —Dieses Problem ist einfacher! Notation r � nennen wir die Ableitung von r nach c, und schreiben: r � = ∂c r Also muss gelten: L(∂c r ) = { cs | c:cs ∈ L r }. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 362 11 · Fallstudie: Reguläre Ausdrücke Die Ableitung regulärer Ausdrücke · 11.4 Was bleibt zu tun? � Wenn wir ∂c r für jedes Symbol c und jeden regulären Ausdruck r berechnen können, ist das Wortproblem weitgehend gelöst. Denn z.B. [c1 , c2 , c3 ] ∈ L r � � [c2 , c3 ] ∈ L ∂c1 r ⇔ � � [c3 ] ∈ L ∂c2 (∂c1 r ) ⇔ � � [ ] ∈ L ∂c3 (∂c2 (∂c1 r )) ⇔ � Damit haben wir unser Problem gelöst, sofern wir nun noch entscheiden können, ob ein regulärer Ausdruck die leere Eingabe [] akzeptiert. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 363 11 · Fallstudie: Reguläre Ausdrücke � Die Ableitung regulärer Ausdrücke · 11.4 Diesen sogenannten nullable Test kann man tatsächlich sehr einfach ausführen: 1 nullable :: RegEx α -> Bool 2 3 4 5 nullable RxNone = False nullable RxEpsilon = True nullable (RxSym _) = False — akzeptiert keine Eingabe, auch nicht die leere — akzeptiert genau die leere Eingabe — hier muss genau ein Buchstabe stehen 6 7 8 9 nullable (RxRep r) = nullable (RxSeq r s) = nullable (RxAlt r s) = ? ? ? Frage Wie lauten die fehlenden Gleichungen? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 364 11 · Fallstudie: Reguläre Ausdrücke � Die Ableitung regulärer Ausdrücke · 11.4 Sei derive :: Eq α ⇒ RegEx α → α → RegEx α die Haskell-Implementation von ∂, also ∂x r = derive r x. � Dann ist match ein Einzeiler: 1 2 match :: Eq α => RegEx α -> [α] -> Bool match r = nullable . foldl derive r Fragen Warum fold-left? Stefan Klinger · DBIS Wofür wird Eq α benötigt? Informatik 2 · Sommer 2016 365 11 · Fallstudie: Reguläre Ausdrücke Die Ableitung regulärer Ausdrücke · 11.4 Beispiel � foldl � foldl � foldl � foldl foldl derive r [c0 , c1 , c2 ] foldl derive (r ‘derive‘ c0 ) [c1 , c2 ] foldl derive ((r ‘derive‘ c0 ) ‘derive‘ c1 ) [c2 ] foldl derive (((r ‘derive‘ c0 ) ‘derive‘ c1 ) ‘derive‘ c2 ) [] ((r ‘derive‘ c0 ) ‘derive‘ c1 ) ‘derive‘ c2 Frage Verhält sich match r [] richtig, d.h. wird auch die leere Eingabe korrekt gematcht? Stefan Klinger · DBIS Informatik 2 · Sommer 2016 366 11 · Fallstudie: Reguläre Ausdrücke Die Ableitung regulärer Ausdrücke · 11.4 Die Ableitungsfunktion ∂ � 1 Es verbleibt die Implementation von derive: derive :: Eq α => RegEx α -> α -> RegEx α 2 3 4 5 derive (RxSym c) x | c == x = rxEpsilon — Hier brauchen wir Eq α 6 7 8 derive r@(RxRep s) x = derive s x ‘rxSeq‘ r 9 10 11 derive (RxAlt r s) x = derive r x ‘rxAlt‘ derive s x 12 13 14 15 16 17 derive (RxSeq r s) x — Dies ist der einzige trickreiche Schritt | nullable r = (derive r x ‘rxSeq‘ s) ‘rxAlt‘ derive s x | otherwise = derive r x ‘rxSeq‘ s � 18 19 20 derive _ _ = rxNone Stefan Klinger · DBIS — Alle anderen Fälle: ε, ∅, RxSym c | c�=x. Informatik 2 · Sommer 2016 367 11 · Fallstudie: Reguläre Ausdrücke Die Ableitung regulärer Ausdrücke · 11.4 Beispiel Die Ableitung derive implementiert tatsächlich unsere Idee (cf. Seite 362, unten) = = = = L (∂x ε) = { cs | x:cs ∈ L ε } = { cs | x:cs ∈ {[]} } = ∅ = L∅ = = = = = Stefan Klinger · DBIS L (∂x x) { cs | x:cs ∈ L x } { cs | x:cs ∈ {[x]} } {[]} Lε L (∂x (r |s)) { cs | x:cs ∈ L (r |s) } { cs | x:cs ∈ L r ∪ L s } { cs | x:cs ∈ L r } ∪ { cs | x:cs ∈ L s } L (∂x r ) ∪ L (∂x s) L (∂x r |∂x s) Informatik 2 · Sommer 2016 368 11 · Fallstudie: Reguläre Ausdrücke 1 2 Die Ableitung regulärer Ausdrücke · 11.4 *Main> r 1, (eps | 1), (0, 0, (0, 0)*) 3 4 5 6 7 8 9 10 11 12 13 14 15 16 *Main> derive it 1 — im GHCi ist it das Ergebnis der letzten Berechnung (eps | 1), (0, 0, (0, 0)*) *Main> derive it 0 0, (0, 0)* *Main> derive it 0 (0, 0)* *Main> derive it 1 {} *Main> foldl derive r [1,0] 0, (0, 0)* *Main> nullable it False 17 18 19 20 21 *Main> foldl derive r [1,0,0] (0, 0)* *Main> nullable it True 22 23 24 25 26 *Main> match r [0,1,0,1,0] False *Main> match r [1,1,0,0,0,0] True Stefan Klinger · DBIS Informatik 2 · Sommer 2016 369 12 Lazy Evaluation 12 · Lazy Evaluation � Im λ-Kalkül ist die Reihenfolge der Reduktionsschritte (�) für einen Ausdruck nicht a priori festgelegt. � Bisher haben wir nur intuitiv erklärt, wie lazy evaluation arbeitet: Teilausdrücke werden erst bei Bedarf reduziert. � Im Folgenden werden wir genauer betrachten • was das bedeutet (→ nicht-strikte Semantik), • und wie das implementiert werden kann (→ Auswertestrategien). Stefan Klinger · DBIS Informatik 2 · Sommer 2016 371 12 · Lazy Evaluation 12.1 � Strikte und nicht-strikte Semantik · 12.1 Strikte und nicht-strikte Semantik Strikte Programmiersprachen beginnen die Auswertung eines Ausdrucks bei der innersten Anwendung. f (g x) � —zuerst wird g x reduziert Nicht-strikte Sprachen fangen dagegen mit der äußersten Anwendung an. —zuerst wird f (...) reduziert f (g x) � Wichtig Hier liegt ein semantischer Unterschied vor, d.h., strikte und nicht-strikte Semantik ordnen dem gleichen Term unter Umständen verschiedene Bedeutungen zu. Stefan Klinger · DBIS Informatik 2 · Sommer 2016 372 12 · Lazy Evaluation Strikte und nicht-strikte Semantik · 12.1 Beispiel Sei g x = ⊥ eine nicht terminierende (oder undefinierte) Berechnung, und sei f = λy . 3. Der Term f (g x) hat unter... ... strikter Semantik den Wert ⊥, weil vor der Reduktion von f (...) das Argument (endlos) reduziert wird. ... nicht-strikter Semantik den Wert 3, weil zuerst die Anwendung von f reduziert wird: f (g x) = (λy . 3) (g x) � 3 β Beispiele � Haskell hat nicht-strikte Semantik. 1 2 Prelude> take 10 [1..] [1,2,3,4,5,6,7,8,9,10] • Ebenso Miranda (ein direkter Haskell-Vorgänger) und Clean. � Die meisten Programmiersprachen sind jedoch strikt, z.B., C, Java, ML (eine funktionale Sprache die starken Einfluss auf Haskell hatte). Stefan Klinger · DBIS Informatik 2 · Sommer 2016 373 12 · Lazy Evaluation Strikte und nicht-strikte Semantik · 12.1 Definition Strikte Funktion Eine Funktion f heisst strikt, genau dann wenn x �⊥ f x �⊥ ⇒ andernfalls heisst die Funktion nicht-strikt. � In einer strikten Programmiersprache sind alle Funktionen strikt. • Im Gegensatz zu nicht-strikten Sprachen lässt sich das Konstrukt if · then · else · fi nicht als Funktion :: Bool → α → α → α ausdrücken. � 1 2 In einer nicht-strikten Sprache darf eine Funktion auch dann einen Wert zurückgeben, wenn ihr Argument ⊥ ist. Prelude> const 3 $ length [1..] 3 1 2 Prelude> const 3 undefined — ⊥ 3 Das muss allerdings nicht für jede Funktion gelten, (1 +) ist z.B. strikt: 1 2 Prelude> 1 + length [1..] — terminiert nicht Stefan Klinger · DBIS 1 2 Prelude> 1 + undefined *** Exception: Prelude.undefined Informatik 2 · Sommer 2016 374