haskell - 我如何构造一个函数/类型来观察这个状态机中的每个转换?

标签 haskell finite-automata

我的问题的要点是我有一个确定性状态自动机,它根据一系列移动进行转换,我希望这个转换序列作为另一个函数的“计算上下文”。这个另一个函数会在每次转换时观察状态机,并用它做一些计算,模糊地让人想起模型 View 模式。简单地说,这个其他函数可能只是读取机器所处的当前状态,并将其打印到屏幕上。

我的状态机实现:

data FA n s = FA { initSt1 :: n, endSt1 :: [n], trans1 :: n -> s -> n }

-- | Checks if sequence of transitions arrives at terminal nodes
evalFA :: Eq n => FA n s -> [s] -> Bool
evalFA fa@(FA _ sfs _ ) = (`elem` sfs) . (runFA fa)

-- | Outputs final state reached by sequence of transitons
runFA :: FA n s -> [s] -> n
runFA (FA s0 sfs trans) = foldl trans s0

和例子:
type State = String
data Trans = A | B | C | D | E

fa :: FA State Trans
fa = FA ("S1") ["S4","S5"] t1

-- | Note non-matched transitions automatically goes to s0
t1 :: State -> Trans -> State
t1 "S1" E = "S1"
t1 "S1" A = "S2"
t1 "S2" B = "S3"
t1 "S2" C = "S4"
t1 "S3" D = "S5"
t1 _  _   = "S1"

runFA fa [A,B]   -- | S3

最佳答案

我将把这个答案分成两部分。第一部分将回答您的原始问题,第二部分将回答您在评论中提出的非确定性 FSA 问题。

管道

您可以使用 pipes在计算之间交错效果。首先,我将从您代码的稍微修改版本开始:

data FA n s = FA { initSt1 :: n, endSt1 :: [n], trans1 :: n -> s -> n }

data State = S1 | S2 | S3 | S4 | S5 deriving (Eq, Show)
data Trans = A | B | C | D | E deriving (Read)

fa :: FA State Trans
fa = FA (S1) [S4,S5] t1

-- | Note non-matched transitions automatically goes to s0
t1 :: State -> Trans -> State
t1 S1 E = S1
t1 S1 A = S2
t1 S2 B = S3
t1 S2 C = S4
t1 S3 D = S5
t1 _  _ = S1

唯一的区别是我使用的是枚举而不是 StringState .

接下来,我将您的转换实现为 Pipe :
runFA :: (Monad m, Proxy p) => FA n s -> () -> Pipe (StateP n p) s n m r
runFA (FA _ _ trans) () = forever $ do
    s <- request ()
    n <- get
    put (trans n s)
    respond n

让我们仔细看看 Pipe 的类型:
() -> Pipe (StateP n p) s n m r
                   ^    ^ ^
                   |    | |
 'n' is the state -+    | |
                        | |
          's's come in -+ +- 'n's go out

所以你可以认为这是一个有效的 scanl .它接收到 s 的流s 使用 request并输出 n 的流s 使用 respond .如果我们愿意,它实际上可以直接交错效果,但我会将效果外包给其他处理阶段。

当我们将其表述为 Pipe ,我们可以奢侈地选择我们的输入和输出流。例如,我们可以将输入连接到不纯的 stdin并将输出连接到不纯的 stdout :
import Control.Proxy
import Control.Proxy.Trans.State

main = runProxy $ execStateK (initSt1 fa) $
    stdinS >-> takeWhileD (/= "quit") >-> mapD read >-> runFA fa >-> printD

这是一个处理管道,您可以理解为:
  • 执行以下 Pipe初始状态为 initSt
  • 来自标准输入的流值
  • 继续流式传输,直到这些值之一是 "quit"
  • 申请 read到所有值以将它们转换为 Trans es
  • 通过我们的扫描有限状态自动机运行它们
  • 打印 State s 自动机发出

  • 让我们试试看:
    >>> main
    A
    S1
    B
    S2
    C
    S3
    A
    S1
    quit
    S2
    >>>
    

    注意它是如何返回最后的 State我们的自动机在里面。然后你可以 fmap您对此计算的测试以查看它是否以终端节点结束:
    >>> fmap (`elem` [S1, S2]) main
    A
    S1
    B
    S2
    C
    S3
    A
    S1
    quit
    True
    

    或者,我们也可以将我们的自动机连接到纯输入和输出:
    import Control.Proxy.Trans.Writer
    import Data.Functor.Identity
    
    main = print $ runIdentity $ runProxy $ runWriterK $ execStateK (initSt1 fa) $
        fromListS [A, C, E, A] >-> runFA fa >-> liftP . toListD
    

    该管道说:
  • 在纯计算(即`runIdentity)中运行它并打印纯结果
  • 使用 Writer记录我们访问过的所有州
  • 使用 State跟踪我们当前的状态
  • 纯粹提供预定义的转换列表
  • 通过我们的 FSA
  • 运行这些转换
  • 将输出记录到 Writer , 使用 liftP指定我们的目标是 Writer

  • 让我们也试试这个:
    >>> main
    (S2,[S1,S2,S4,S1])
    

    输出最终状态和访问状态列表。

    列表T

    现在,您提出了第二个问题,即如何进行有效的非确定性计算。 Daniel 实际上是不正确的:您可以使用 pipes 将影响与不确定性交织在一起。 , 也!诀窍是使用 ProduceT ,这是 pipes ListT 的实现.

    首先,我将展示如何使用 ProduceT :
    fsa :: (Proxy p) => State -> Trans -> ProduceT p IO State
    fsa state trans = do
        lift $ putStrLn $ "At State: " ++ show state
        state' <- eachS $ case (state, trans) of
            (S1, A) -> [S2, S3]
            (S2, B) -> [S4, S5]
            (S3, B) -> [S5, S2]
            (S4, C) -> [S2, S3]
            (S5, C) -> [S3, S4]
            (_ , _) -> [S1]
        return state'
    

    上面的代码说:
  • 打印当前状态
  • 不确定地绑定(bind)许多可能的转换
  • 返回新的选中状态

  • 为了避免手动状态传递,我将包装 fsaStateT :
    import qualified Control.Monad.Trans.State as S
    
    fsa2 :: (Proxy p) => Trans -> S.StateT State (ProduceT p IO) State
    fsa2 trans = do
        s <- S.get
        s' <- lift $ fsa s trans
        S.put s'
        return s'
    

    现在我可以使用 mapM 轻松地在多个转换上运行生成器.完成后,我将其编译为 Producer使用 runRespondT :
    use1 :: (Proxy p) => () -> Producer p State IO ()
    use1 () = runRespondT $ (`S.execStateT` S1) $ do
        mapM_ fsa2 [A, B, C]  -- Run the generator using four transitions
    

    这会产生一个管道,其效果是打印它正在遍历的状态,并输出它遇到的最终状态流。我将输出连接到打印阶段,以便我们可以同时观察两者:
    >>> runProxy $ use1 >-> printD
    At State: S1
    At State: S2
    At State: S4
    S2
    S3
    At State: S5
    S3
    S4
    At State: S3
    At State: S5
    S3
    S4
    At State: S2
    S1
    

    我们可以观察自动机的路径以及它如何回溯。它在每一步之后打印出它当前的位置,然后在到达它们时立即发出所有 7 个最终状态。

    对不起,如果这篇文章有点粗糙,但这是我能尽快做的最好的事情。

    关于haskell - 我如何构造一个函数/类型来观察这个状态机中的每个转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16587190/

    相关文章:

    string - 使用 ByteString 的 Haskell HTTP 客户端

    haskell - Haskell 中的严格类型别名

    c++ - dfa 转换函数

    java - 实现非确定性有限自动机 (NFA)

    haskell - 从代理中提取存在性

    haskell - 可折叠的 IntSet

    java - 我可以使用 java.util.Set 在 Java 中为 DFA 实现状态转换吗

    algorithm - 是否有一种有效的算法来确定一个 NFA 接受的语言是否是另一个 NFA 接受的语言的超集?

    prolog - 如何在有限状态机 Prolog 中找到无用状态

    list - 此类型定义是否处理空列表?