summaryrefslogtreecommitdiff
path: root/edit-lens/src/Control
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2018-05-21 16:14:26 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2018-05-21 16:14:26 +0200
commitd3f6fdc3ea71386c2b9a9cd5a686455dbee3e60e (patch)
treeb62e4748b8e058a5ab4122accf6b33e15bdd9b49 /edit-lens/src/Control
parenteb599b2394e62842423cc0bbee2432a9cae95f4b (diff)
downloadincremental-dfsts-d3f6fdc3ea71386c2b9a9cd5a686455dbee3e60e.tar
incremental-dfsts-d3f6fdc3ea71386c2b9a9cd5a686455dbee3e60e.tar.gz
incremental-dfsts-d3f6fdc3ea71386c2b9a9cd5a686455dbee3e60e.tar.bz2
incremental-dfsts-d3f6fdc3ea71386c2b9a9cd5a686455dbee3e60e.tar.xz
incremental-dfsts-d3f6fdc3ea71386c2b9a9cd5a686455dbee3e60e.zip
Introduce FSTs & Generalize input/output
`toFST` is currently invalid
Diffstat (limited to 'edit-lens/src/Control')
-rw-r--r--edit-lens/src/Control/DFST.lhs78
-rw-r--r--edit-lens/src/Control/DFST/Lens.lhs170
-rw-r--r--edit-lens/src/Control/FST.lhs24
3 files changed, 272 insertions, 0 deletions
diff --git a/edit-lens/src/Control/DFST.lhs b/edit-lens/src/Control/DFST.lhs
new file mode 100644
index 0000000..aec7bbb
--- /dev/null
+++ b/edit-lens/src/Control/DFST.lhs
@@ -0,0 +1,78 @@
1\begin{code}
2{-# LANGUAGE ScopedTypeVariables
3#-}
4
5{-|
6Description: Deterministic finite state transducers
7-}
8module Control.DFST
9 ( DFST(..)
10 , runDFST, runDFST'
11 , toFST
12 ) where
13
14import Data.Map.Strict (Map, (!?))
15import qualified Data.Map.Strict as Map
16
17import Data.Set (Set)
18import qualified Data.Set as Set
19
20import Data.Sequence (Seq(..))
21import qualified Data.Sequence as Seq
22
23import Data.Monoid
24
25import Numeric.Natural
26
27import Control.Monad
28import Control.Monad.State
29
30import Control.FST (FST(FST))
31import qualified Control.FST as FST
32
33
34data DFST state input output = DFST
35 { stInitial :: state
36 , stTransition :: Map (state, input) (state, Seq output)
37 -- ^ All @(s, c)@-combinations not mapped are assumed to map to @(s, Nothing)@
38 , stAccept :: Set state
39 }
40
41
42toFST :: forall state input output. (Ord state, Ord input, Ord output) => DFST state input output -> FST (state, Natural) input output
43-- ^ Split apart non-singleton outputs into a series of epsilon-transitions
44--
45-- This function is currently invalid since multiple out-edges in the `DFST` visit the same intermediate states (we should label intermediate states not only with an ordinal but also with the input Symbol from the `DFST`)
46toFST DFST{..} = flip execState initialFST $ forM_ (Map.toList stTransition) handleTransition
47 where
48 initialFST = FST
49 { stInitial = (stInitial, 0)
50 , stTransition = Map.empty
51 , stAccept = Set.map (,0) stAccept
52 }
53 addTransition :: forall state input output. (Ord state, Ord input, Ord output) => (state, Maybe input) -> (state, Maybe output) -> State (FST state input output) ()
54 addTransition k v = modify $ \f@FST{ stTransition } -> f { FST.stTransition = Map.insertWith Set.union k (Set.singleton v) stTransition }
55 handleTransition :: ((state, input), (state, Seq output)) -> State (FST (state, Natural) input output) ()
56 handleTransition ((st, inS), (st', outs)) = handleTransition' (st, 0) (Just inS) outs (st', 0)
57 handleTransition' :: (state, Natural) -> Maybe input -> Seq output -> (state, Natural) -> State (FST (state, Natural) input output) ()
58 handleTransition' from inS Empty to = addTransition (from, inS) (to, Nothing)
59 handleTransition' from inS (outS :<| Empty) to = addTransition (from, inS) (to, Just outS)
60 handleTransition' from@(st, i) inS (outS :<| oo) to = addTransition (from, inS) ((st, succ i), Just outS) >> handleTransition' (st, succ i) Nothing oo to
61
62runDFST :: forall state input output. (Ord state, Ord input) => DFST state input output -> Seq input -> Maybe (Seq output)
63runDFST dfst@DFST{..} str = let (finalState, str') = runDFST' dfst stInitial str Seq.empty
64 in str' <$ guard (finalState `Set.member` stAccept)
65
66runDFST' :: forall state input output. (Ord state, Ord input)
67 => DFST state input output
68 -> state -- ^ Current state
69 -> Seq input -- ^ Remaining input
70 -> Seq output -- ^ Accumulator containing previous output
71 -> (state, Seq output) -- ^ Next state, altered output
72runDFST' _ st Empty acc = (st, acc)
73runDFST' dfst@DFST{..} st (c :<| cs) acc
74 | Just (st', mc') <- stTransition !? (st, c)
75 = runDFST' dfst st' cs $ acc <> mc'
76 | otherwise
77 = runDFST' dfst st cs acc
78\end{code}
diff --git a/edit-lens/src/Control/DFST/Lens.lhs b/edit-lens/src/Control/DFST/Lens.lhs
new file mode 100644
index 0000000..0976314
--- /dev/null
+++ b/edit-lens/src/Control/DFST/Lens.lhs
@@ -0,0 +1,170 @@
1\begin{code}
2{-# LANGUAGE ScopedTypeVariables
3 , TemplateHaskell
4#-}
5
6module Control.DFST.Lens
7 ( StringEdit(..)
8 , StringEdits(..)
9 , insert, delete
10 , DFSTAction(..), DFSTComplement
11 , dfstLens
12 , module Control.DFST
13 , module Control.Lens.Edit
14 ) where
15
16import Control.DFST
17import Control.Lens.Edit
18import Control.Lens
19import Control.Lens.TH
20import Control.Edit
21
22import Numeric.Natural
23import Data.Sequence (Seq((:<|), (:|>)))
24import qualified Data.Sequence as Seq
25
26import Data.Compositions.Snoc (Compositions)
27import qualified Data.Compositions.Snoc as Comp
28
29import Data.Algorithm.Diff (Diff, getDiff)
30import qualified Data.Algorithm.Diff as Diff
31
32import Data.Monoid
33import Data.Function (on)
34import Data.Foldable (toList)
35
36
37data StringEdit char = Insert { _sePos :: Natural, _seInsertion :: char }
38 | Delete { _sePos :: Natural }
39 deriving (Eq, Ord, Show, Read)
40
41makeLenses ''StringEdit
42
43data StringEdits char = StringEdits (Seq (StringEdit char))
44 | SEFail
45 deriving (Eq, Ord, Show, Read)
46
47makePrisms ''StringEdits
48
49stringEdits :: Traversal' (StringEdits char) (StringEdit char)
50stringEdits = _StringEdits . traverse
51
52insert :: Natural -> char -> StringEdits char
53insert n c = StringEdits . Seq.singleton $ Insert n c
54
55delete :: Natural -> StringEdits char
56delete n = StringEdits . Seq.singleton $ Delete n
57
58instance Monoid (StringEdits char) where
59 mempty = StringEdits Seq.empty
60 SEFail `mappend` _ = SEFail
61 _ `mappend` SEFail = SEFail
62 (StringEdits Seq.Empty) `mappend` x = x
63 x `mappend` (StringEdits Seq.Empty) = x
64 (StringEdits x@(bs :|> b)) `mappend` (StringEdits y@(a :<| as))
65 | (Insert n _) <- a
66 , (Delete n') <- b
67 , n == n'
68 = StringEdits bs `mappend` StringEdits as
69 | otherwise = StringEdits $ x `mappend` y
70
71instance Module (StringEdits char) where
72 type Domain (StringEdits char) = Seq char
73 apply str SEFail = Nothing
74 apply str (StringEdits Seq.Empty) = Just str
75 apply str (StringEdits (es :|> Insert n c)) = (flip apply) (StringEdits es) =<< go str n c
76 where
77 go Seq.Empty n c
78 | n == 0 = Just $ Seq.singleton c
79 | otherwise = Nothing
80 go str@(x :<| xs) n c
81 | n == 0 = Just $ c <| str
82 | otherwise = (x <|) <$> go xs (pred n) c
83 apply str (StringEdits (es :|> Delete n)) = (flip apply) (StringEdits es) =<< go str n
84 where
85 go Seq.Empty _ = Nothing
86 go (x :<| xs) n
87 | n == 0 = Just xs
88 | otherwise = (x <|) <$> go xs (pred n)
89
90 init = Seq.empty
91 divInit = StringEdits . Seq.unfoldl go . (0,)
92 where
93 go (_, Seq.Empty) = Nothing
94 go (n, (c :<| cs)) = Just ((succ n, cs), Insert n c)
95
96\end{code}
97
98% TODO Make notation mathy
99
100Um zunächst eine asymmetrische edit-lens `StringEdits -> StringEdits` mit akzeptabler Komplexität für einen bestimmten `DFST s` (entlang der \emph{Richtung} des DFSTs) zu konstruieren möchten wir folgendes Verfahren anwenden:
101
102Gegeben eine Sequenz (`StringEdits`) von zu übersetzenden Änderungen genügt es die Übersetzung eines einzelnen `StringEdit`s in eine womöglich längere Sequenz von `StringEdits` anzugeben, alle `StringEdits` aus der Sequenz zu übersetzen (hierbei muss auf die korrekte Handhabung des Komplements geachtet werden) und jene Übersetzungen dann zu concatenieren.
103
104Wir definieren zunächst die \emph{Wirkung} eines DFST auf einen festen String als eine Abbildung `state -> (state, String)`, die den aktuellen Zustand vorm Parsen des Strings auf den Zustand danach und die (womöglich leere) Ausgabe schickt.
105Diese Wirkungen bilden einen Monoiden analog zu Endomorphismen, wobei die Resultat-Strings concateniert werden.
106
107Die Unterliegende Idee ist nun im Komplement der edit-lens eine Liste von Wirkungen (eine für jedes Zeichen der Eingabe des DFSTs) und einen Cache der monoidalen Summen aller kontinuirlichen Teillisten zu halten.
108Da wir wissen welche Stelle im input-String von einem gegebenen edit betroffen ist können wir, anhand der Wirkung des Teilstücks bis zu jener Stelle, den output-String in einen durch den edit unveränderten Prefix und einen womöglich betroffenen Suffix unterteilen.
109Die Wirkung ab der betroffenen Stelle im input-String können wir also Komposition der Wirkung der durch den edit betroffenen Stelle und derer aller Zeichen danach bestimmen.
110Nun gilt es nur noch die Differenz (als `StringEdits`) des vorherigen Suffixes im output-String und des aus der gerade berechneten Wirkung Bestimmten zu bestimmen.
111
112
113% Für die Rückrichtung bietet es sich an eine Art primitive Invertierung des DFSTs zu berechnen.
114% Gegeben den aktuellen DFST $A$ möchten wir einen anderen $A^{-1}$ finden, sodass gilt:
115
116% \begin{itemize}
117% \item $A^{-1}$ akzeptiert einen String $s^{-1}$ (endet seinen Lauf in einem finalen Zustand) gdw. es einen String $s$ gibt, der unter $A$ die Ausgabe $s^{-1}$ produziert.
118% \item Wenn $A^{-1}$ einen String $s^{-1}$ akzeptiert so produziert die resultierende Ausgabe $s$ unter $A$ die Ausgabe $s^{-1}$.
119% \end{itemize}
120
121% Kann nicht funktionieren, denn $A^{-1}$ ist notwendigerweise nondeterministisch. Wird $A^{-1}$ dann zu einem DFST forciert (durch arbiträre Wahl einer Transition pro Zustand) gehen Informationen verloren—$A^{-1}$ produziert nicht den minimale edit auf dem input string (in der Tat beliebig schlecht) für einen gegeben edit auf dem output string.
122
123% Stelle im bisherigen Lauf isolieren, an der edit im output-string passieren soll, breitensuche auf pfaden, die sich von dieser stelle aus unterscheiden?
124% Gegeben einen Pfad und eine markierte Transition, finde Liste aller Pfade aufsteigend sortiert nach Unterschied zu gegebenem Pfad, mit Unterschieden "nahe" der markierten Transition zuerst — zudem jeweils edit auf dem Eingabestring
125% Einfacher ist Breitensuche ab `stInitial` und zunächst diff auf eingabe-strings.
126
127\begin{code}
128
129data DFSTAction state input output = DFSTAction { runDFSTAction :: state -> (state, Seq output) }
130
131instance Monoid (DFSTAction state input output) where
132 mempty = DFSTAction $ \x -> (x, Seq.empty)
133 (DFSTAction f) `mappend` (DFSTAction g) = DFSTAction $ \s -> let ((f -> (s', out')), out) = g s in (s', out <> out')
134
135type DFSTComplement state input output = Compositions (DFSTAction state input output)
136
137dfstLens :: forall state input output. (Ord state, Ord input, Ord output) => DFST state input output -> EditLens (DFSTComplement state input output) (StringEdits input) (StringEdits output)
138dfstLens dfst@DFST{..} = EditLens ground propR propL
139 where
140 ground :: DFSTComplement state input output
141 ground = Comp.fromList []
142
143 propR :: (DFSTComplement state input output, StringEdits input) -> (DFSTComplement state input output, StringEdits output)
144 propR (c, SEFail) = (c, SEFail)
145 propR (c, StringEdits (es :|> e)) = (c', es' <> es'')
146 where
147 (cSuffix, cPrefix) = Comp.splitAt (Comp.length c - (e ^. sePos . from enum)) c
148 cSuffix'
149 | Delete _ <- e = Comp.take (pred $ Comp.length cSuffix) cSuffix -- TODO unsafe
150 | Insert _ nChar <- e = cSuffix <> Comp.singleton (DFSTAction $ \x -> runDFST' dfst x (pure nChar) Seq.empty)
151 (pState, pOutput) = runDFSTAction (Comp.composed cPrefix) stInitial
152 (_, sOutput ) = runDFSTAction (Comp.composed cSuffix ) pState
153 (_, sOutput') = runDFSTAction (Comp.composed cSuffix') pState
154 (c', es') = propR (cSuffix' <> cPrefix, StringEdits es)
155 es'' = strDiff sOutput sOutput' & stringEdits . sePos . from enum +~ Seq.length pOutput
156 propR (c, StringEdits Seq.Empty) = (c, mempty)
157
158
159 propL :: (DFSTComplement state input output, StringEdits output) -> (DFSTComplement state input output, StringEdits input)
160 propL = undefined
161
162strDiff :: forall sym. Eq sym => Seq sym -> Seq sym -> StringEdits sym
163-- ^ @strDiff a b@ calculates a set of edits, which, when applied to @a@, produce @b@
164strDiff a b = snd . foldr toEdit (0, mempty) $ (getDiff `on` toList) a b
165 where
166 toEdit :: Diff sym -> (Natural, StringEdits sym) -> (Natural, StringEdits sym)
167 toEdit (Diff.Both _ _) (n, es) = (succ n, es)
168 toEdit (Diff.First _ ) (n, es) = (succ n, delete n <> es)
169 toEdit (Diff.Second c) (n, es) = (succ (succ n), insert n c <> es)
170\end{code}
diff --git a/edit-lens/src/Control/FST.lhs b/edit-lens/src/Control/FST.lhs
new file mode 100644
index 0000000..d3c8ca9
--- /dev/null
+++ b/edit-lens/src/Control/FST.lhs
@@ -0,0 +1,24 @@
1\begin{code}
2
3{-|
4Description: Finite state transducers with epsilon-transitions
5-}
6module Control.FST
7 ( FST(..)
8 ) where
9
10import Data.Map.Strict (Map)
11import qualified Data.Map.Strict as Map
12
13import Data.Set (Set)
14
15import Data.Sequence (Seq)
16
17import Control.Lens.TH
18
19data FST state input output = FST
20 { stInitial :: state
21 , stTransition :: Map (state, Maybe input) (Set (state, Maybe output))
22 , stAccept :: Set state
23 }
24\end{code}