「arrows」パッケージのControl.Arrow.Transformer.Automatonモジュールを確認してください。タイプはこんな感じ
newtype Automaton a b c = Automaton (a b (c, Automaton a b c))
アロートランスフォーマーであるため、これは少し混乱します。最も単純なケースでは、次のように書くことができます
type Auto = Automaton (->)
これは、基になる矢印として関数を使用します。Automaton定義の「a」を(->)に置き換え、中置記法を使用すると、これはおおよそ次のようになります。
newtype Auto b c = Automaton (b -> (c, Auto b c))
言い換えれば、オートマトンは、入力を受け取り、結果と新しいオートマトンを返す関数です。
これを直接使用するには、引数を取り、結果と次の関数を返す状態ごとに関数を記述します。たとえば、これは正規表現 "a + b"(つまり、一連の少なくとも1つの'a'とそれに続く'b')を認識するステートマシンです。(注:テストされていないコード)
state1, state2 :: Auto Char Bool
state1 c = if c == 'a' then (False, state2) else (False, state1)
state2 c = case c of
'a' -> (False, state2)
'b' -> (True, state1)
otherwise -> (False, state1)
元の質問では、Q = {state1、state2}、X = Char、deltaは関数適用、FはTrueを返す状態遷移です(「受け入れ状態」ではなく、出力遷移を使用しました。値を受け入れる)。
または、矢印表記を使用することもできます。Automatonは、LoopやCircuitを含むすべての興味深い矢印クラスのインスタンスであるため、delayを使用して以前の値にアクセスできます。(注:ここでも、テストされていないコード)
recognise :: Auto Char Bool
recognise = proc c -> do
prev <- delay 'x' -< c -- Doesn't matter what 'x' is, as long as its not 'a'.
returnA -< (prev == 'a' && c == 'b')
「遅延」矢印は、「前」が現在の値ではなく「c」の前の値と等しいことを意味します。「rec」を使用して、以前の出力にアクセスすることもできます。たとえば、これは時間の経過とともに減衰する合計を示す矢印です。(この場合、実際にテストされています)
-- | Inputs are accumulated, but decay over time. Input is a (time, value) pair.
-- Output is a pair consisting
-- of the previous output decayed, and the current output.
decay :: (ArrowCircuit a) => NominalDiffTime -> a (UTCTime, Double) (Double, Double)
decay tau = proc (t2,v2) -> do
rec
(t1, v1) <- delay (t0, 0) -< (t2, v)
let
dt = fromRational $ toRational $ diffUTCTime t2 t1
v1a = v1 * exp (negate dt / tau1)
v = v1a + v2
returnA -< (v1a, v)
where
t0 = UTCTime (ModifiedJulianDay 0) (secondsToDiffTime 0)
tau1 = fromRational $ toRational tau
「delay」への入力に、その出力から派生した値である「v」がどのように含まれているかに注意してください。「rec」句はこれを可能にするので、フィードバックループを構築できます。