我的问题的要点是我有一个确定性状态自动机,它根据一个移动列表进行转换,我希望这个转换序列充当另一个函数的“计算上下文”。这个其他函数会在每次转换时观察状态机,并用它做一些计算,模糊地让人联想到模型视图模式。通常,这个其他功能可能只是读取机器所处的当前状态,并将其打印到屏幕上。
我的状态机实现:
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
答案 0 :(得分:9)
我将把这个答案分成两部分。第一部分将回答您的原始问题,第二部分将回答您在评论中提出的非确定性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
唯一的区别是我使用枚举而不是String
State
。
接下来,我将您的转场实施为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
接收request
s流,并使用n
输出respond
s流。如果我们想要,它实际上可以直接交错效果,但我会将效果外包给其他处理阶段。
当我们将其表示为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
我们试一试:
>>> 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
那条管道说:
Writer
记录我们访问过的所有州State
跟踪我们当前的状态Writer
将输出记录到liftP
,以指定我们定位Writer
我们也试试这个:
>>> main
(S2,[S1,S2,S4,S1])
输出最终状态和访问状态列表。
现在,您提出了第二个问题,即您如何进行有效的非确定性计算。 Daniel实际上是错误的:您也可以使用pipes
将效果与非确定性交错!诀窍是使用ProduceT
,这是pipes
的{{1}}实现。
首先,我将展示如何使用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'
包裹fsa
:
StateT
现在,我可以使用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
我们可以观察到自动机的路径以及它如何回溯。它打印出每个步骤后的当前位置,然后在它们到达时立即发出所有7个最终状态。
很抱歉,如果这篇文章有点粗糙,但这是我能赶时间做的最好的。