【发布时间】:2018-11-03 20:50:15
【问题描述】:
我想使用 Haskell 进行有限状态机分析和记录。我希望库足够通用,以便在实例化特定 FSM 时需要很少的样板。
状态机定义基于状态s、事件e 和动作a。主要要求是:
- 将机器显示为文本(定义或派生
Show)。 - 以花哨的形式显示,例如为 Graphviz 发出
dot符号。 - 确定有向图的正确性(对于正确性的某些定义)、终止条件和其他属性。
- 使用状态机定义来帮助确保以其他语言实现 FSM 的测试覆盖率(源代码生成、测试覆盖率分析)。
我最初的实现是这样的:
import Data.List
data StateMachine s e a =
StateMachine {
states :: [s] -- ^states that the machine can be in
, events :: [e] -- ^events that the machine can process
, actions :: [a] -- ^actions the machine can perform
, initialStates :: [s] -- ^starting states
, transitions :: [((s,e),(a,s))] -- ^state transitions
}
-- |Find the action and next state for an event in the given state
nextOperation :: (Ord s, Ord e, Eq a) =>
StateMachine s e a -> s -> e -> Maybe (a, s)
nextOperation sm st ev = lookup (st, ev) (transitions sm)
-- |Find the next state from this state for an event, if present
nextTransition :: (Ord s, Ord e, Eq a) => StateMachine s e a -> s -> e -> Maybe s
nextTransition sm st ev = fmap snd $ nextOperation sm st ev
allNextStates :: (Ord s, Ord e, Eq a) => StateMachine s e a -> s -> [e] -> [s]
allNextStates _ _ [] = []
allNextStates sm st (ev:es) = case nextTransition sm st ev of
Just st' -> st': allNextStates sm st es
Nothing -> allNextStates sm st es
-- |Compute the set of states reachable from a state
reachableStates :: (Ord s, Ord e, Eq a) => StateMachine s e a -> s -> [s]
reachableStates sm st = nub $ allNextStates sm st (events sm)
-- |Compute the transitive closure from the initial states
transitiveClosure :: (Ord s, Ord e, Eq a) => StateMachine s e a -> [s]
transitiveClosure sm = transitives sm (initialStates sm) (initialStates sm)
where
transitives :: (Ord s, Ord e, Eq a) => StateMachine s e a -> [s] -> [s] -> [s]
transitives _ [] reach = reach
transitives stm reachable@(st: sts) reach =
let r = reachableStates stm st
rs = [r' | r' <- r, not (r' `elem` reach)]
in
if null rs
then transitives stm sts reach
else transitives stm (sts ++ rs) (reach ++ rs)
从那里开始,几乎所有测试可达性所需的操作和传递闭包都可以轻松构建。但是,我最终到处都遇到了(Ord s, Ord e, Eq a) 约束,并且一直遇到“模棱两可的类型”问题。这也让我很恼火,因为我可以很容易地使用 Java 中的抽象类来定义它,比方说。
我的第二种方法是使用类型族,但这并不顺利。第一步是定义类:
{-# language TypeFamilies #-}
{-# language MultiParamTypeClasses #-}
...
class (Ord s, Show s) => SMstate s
class (Ord e, Show e) => SMevent e
class (Eq a, Show a) => SMaction a
class (SMstate s, SMevent e, SMaction a) => MachineState s e a where
data MS s e a
allEvents :: MS s e a -> [e]
initialStates :: MS s e a -> [s]
allStates :: MS s e a -> [s]
allActions :: MS s e a -> [a]
nextOperation :: MS s e a -> s -> e -> Maybe (a,s)
nextTransition :: MS s e a -> s -> e -> Maybe s
nextTransition sm st ev = fmap snd $ nextOperation sm st ev
nextState :: MS s e a -> s -> e -> s
nextState sm st ev = case nextTransition sm st ev of
Just st' -> st'
Nothing -> st
allNextStates :: MS s e a -> s -> [e] -> [s]
allNextStates _ _ [] = []
allNextStates sm st (ev:evs) =
let nextStates = allNextStates sm st evs
in (maybeToList $ nextTransition sm st ev) ++ nextStates
reachableStates :: MS s e a -> s -> [s]
reachableStates sm s = nub $ allNextStates sm s (allEvents sm)
transitiveClosure :: MS s e a -> [s]
transitiveClosure sm = transitivesOf sm (initialStates sm) (initialStates sm)
where
transitivesOf :: MachineState s e a => MS s e a -> [s] -> [s] -> [s]
transitivesOf _ [] reach = reach
transitivesOf sm reachable@(st:sts) reach =
let r = reachableStates sm st
rs = [r' | r' <- r, r' `notElem` reach]
in
if null rs
then transitivesOf sm sts reach
else transitivesOf sm (sts ++ rs) (reach ++ rs)
由于类约束与MachineState 周围的定义相关联,我不再需要担心传播约束。
类型族是解决这个问题的方法吗?如何将StateMachine 连接到MachineState 类?
【问题讨论】:
-
(Ord s, Ord e, Eq a)约束无处不在有什么问题?对我来说,在类型签名中看到它告诉我很多我可能想知道的关于值的事情。您可以将其缩写为例如type Machine s e a = (Ord s, Ord e, Eq a)如果您只是不喜欢重复输入。 -
如果您有一个只需要状态
s的函数,并且它通过调用nextOperation来实现,您必须提供三个约束。 GHC 不会编译这样的函数,因为它不使用e或a,现在你必须做后空翻才能让它编译。 -
您的陈述是正确的,但只是空洞的:如果您有一个调用
nextOperation的函数,那么它只需要状态s是不正确的。所以你的“如果”的前提是错误的。对我来说,这听起来像是一个 XY 问题。你为什么不把你实际遇到的问题的细节而不是这个模糊的替代品?向我们展示您实际遇到问题的代码。 -
recent question 可能是相关的。
-
您发布的代码在这里编译得很好。如果您在遇到问题时需要帮助,您需要说明什么不起作用(您希望会起作用)以及您遇到的错误。
标签: haskell