编码状态机,其节点可以是其他嵌套机器

时间:2016-10-08 10:46:22

标签: haskell

我正在尝试建模状态机,其节点可以是Haskell中具有调用堆栈的其他机器。

例如Bar是一个由

编码的相当简单的机器
module Bar (BarState(..), run) where
import Cont

data BarState = BInit | BMid | BEnd deriving Show

run BInit = Cont BMid
run BMid = End BEnd

Cont类型描述了状态转换:

module Cont (Cont(..)) where

data Cont where
  -- continue in the same machine
  Cont  :: Show s => s -> Cont

  -- fork a new machine
  Start :: (Show s, Show s') => s -> s' -> Cont

  -- final state
  End   :: Show s => s -> Cont

我们可以

  • 通过Cont s
  • 继续在同一台计算机上
  • 致电新计算机Start s s'
  • s
  • 结束状态End s的机器

Bar不会调用任何其他流量,因此它仅使用ContEnd,但Foo是一个调用Bar的计算机:

module Foo (FooState(..), run) where
import qualified Bar as Bar
import Cont

data FooState = FInit | FBar | FEnd deriving Show

run FInit = Start FBar Bar.BInit
run FBar = End FEnd

FooFlow is a node in BarFlow

机器可以处于一种状态,任何机器都可以调用另一台机器(Start s s')。我用一堆状态来描述我的整个用户状态:

import qualified Bar as Bar
import qualified Foo as Foo
import Cont

data Stack s = Empty | s ::: Stack s ; infixr 5 :::

data States = FooState Foo.FooState | BarState Bar.BarState

run :: Stack States -> Stack States
run Empty = error "Empty stack"
run (s ::: rest) = proceed (run' s) rest

run' :: States -> Cont
run' (FooState s) = Foo.run s
run' (BarState s) = Bar.run s

proceed :: Cont -> Stack States -> Stack States
proceed (Cont s) rest = undefined ::: rest
proceed (Start s s') rest = undefined ::: undefined ::: rest
proceed (End s) rest = rest

问题是我无法在s构造函数中的Cont上进行模式匹配。

我的最终目标是拥有一个可序列化的堆栈,使我能够从任何有效状态继续流程。例如:

run [FInit] -> [BInit, FInit]
run [BInit, FInit] -> [BEnd, FInit]
run [BMid, FInit] -> [BEnd, FInit]
run [BEnd, FInit] -> [FEnd]

此示例中的模块源代码可用here

可能有更好的方法来编码这个模型,我不仅限于我的。

1 个答案:

答案 0 :(得分:3)

您不需要模式匹配。据我所知,你需要的只是执行步骤和序列化/反序列化。这可以在不知道确切类型的情况下完成。

{-# LANGUAGE UnicodeSyntax #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}

module States (
    ) where

import Prelude.Unicode

import Control.Arrow
import Text.Read (Read(readsPrec), readMaybe)

-- | State is something, which have next action and string representation
data State = State {
    next ∷ Cont,
    save ∷ String }

instance Show State where
    show = save

-- | Stack is list of states
type Stack = [State]

-- | Action operates on 'State'
data Cont = Cont State | Start State State | End State deriving (Show)

-- | Converts actual data to 'State'
cont ∷ IsState s ⇒ s → Cont
cont = Cont ∘ state

start ∷ (IsState s, IsState s') ⇒ s → s' → Cont
start x x' = Start (state x) (state x')

end ∷ IsState s ⇒ s → Cont
end = End ∘ state

run ∷ Stack → Stack
run [] = error "empty stack"
run (s : ss) = proceed (next s) ss

proceed ∷ Cont → Stack → Stack
proceed (Cont s) rest = s : rest
proceed (Start s s') rest = s' : s : rest
proceed (End s) rest = rest

serialize ∷ Stack → [String]
serialize = map save

-- | Here we have to provide some type in order to know, which
-- read functions to use
deserialize ∷ IsState s ⇒ [String] → Maybe [s]
deserialize = mapM readMaybe

class (Read s, Show s) ⇒ IsState s where
    step ∷ s → Cont
    -- | No need of implementation, just to allow using when implementing step
    state ∷ s → State
    state x = State (step x) (show x)

-- | Convert actual data to stack
stack ∷ IsState s ⇒ [s] → Stack
stack = map state

-- | Union of states, to specify type of 'deserialize'
data BiState l r = LState l | RState r

instance (Read l, Read r) ⇒ Read (BiState l r) where
    readsPrec p s = map (first LState) (readsPrec p s) ++ map (first RState) (readsPrec p s)

instance (Show l, Show r) ⇒ Show (BiState l r) where
    show (LState x) = show x
    show (RState y) = show y

instance (IsState l, IsState r) ⇒ IsState (BiState l r) where
    step (LState x) = step x
    step (RState y) = step y

-- Test data

data BarState = BInit | BMid | BEnd deriving (Read, Show)
data FooState = FInit | FBar | FEnd deriving (Read, Show)

instance IsState BarState where
    step BInit = cont BMid
    step BMid = end BEnd

instance IsState FooState where
    step FInit = start FBar BInit
    step FBar = end FEnd

-- Usage

test' ∷ IO ()
test' = do
    let
        start' = [state FInit]
        next' = run start'
    print next' -- [BInit,FBar]
    let
        saved' = serialize next'
        Just loaded' = deserialize saved' ∷ Maybe [BiState FooState BarState]
        next'' = run next'
    print next'' -- [BMid,FBar]
    print $ run $ stack loaded' -- [BMid,FBar] too
    let
        go [] = putStrLn "done!"
        go st = print st' >> go st' where
            st' = run st
    go next''

-- output:
-- [BInit,FBar]
-- [BMid,FBar]
-- [BMid,FBar]
-- [FBar]
-- []
-- done!