编辑: 用户@apocalisp和@BenjaminHodgson在下面留下了出色的答案,跳过阅读大部分问题并转到他们的答案。
问题概述:如何从第一张图片(FSM状态组合爆炸)到达第二张图片(在继续前需要访问所有状态)。
我想建立一个有限状态机(实际上是在Haskell中,但我首先尝试使用Idris来看看它是否能指导我的Haskell),其中有一些中间状态必须在到达最终状态之前进行访问。如果我可以任意地限制某些状态的谓词,那就太好了。
在下面的图片中,有一个Initial
状态、3个中间状态:A、B、C
和一个Final
状态。如果我没有弄错的话,在“正常”的FSM中,你总是需要n!
个中间状态来表示每条可能路径的组合。
这是不可取的。
相反,使用类型族和可能是依赖类型,我认为应该有一种状态被携带,并且只有当它通过某些谓词时,您才能被允许前往最终状态。(这是不是使它成为下推自动机而不是FSM?)
到目前为止,我的代码(idris),类比于添加制作沙拉的配料,顺序无关紧要,但它们都需要加入:
data SaladState = Initial | AddingIngredients | ReadyToEat
record SaladBowl where
constructor MkSaladBowl
lettuce, tomato, cucumber : Bool
data HasIngredient : (ingredient : SaladBowl -> Bool) -> (bowl : SaladBowl ** ingredient bowl = True) -> Type where
Bowl : HasIngredient ingredient bowl
data HasIngredients : (ingredients : List (SaladBowl -> Bool))
-> (bowl : SaladBowl ** (foldl (&&) True (map (\i => i bowl) ingredients) = True))
-> Type where
Bowlx : HasIngredients ingredients bowl
data SaladAction : (ty : Type) -> SaladState -> (ty -> SaladState) -> Type where
GetBowl : SaladAction SaladBowl Initial (const Initial)
AddLettuce : SaladBowl -> SaladAction (bowl ** HasIngredient lettuce bowl) st (const AddingIngredients)
AddTomato : SaladBowl -> SaladAction (bowl ** HasIngredient tomato bowl) st (const AddingIngredients)
AddCucumber : SaladBowl -> SaladAction (bowl ** HasIngredient cucumber bowl) st (const AddingIngredients)
MixItUp : SaladBowl -> SaladAction (bowl ** (HasIngredients [lettuce, tomato, cucumber] bowl)) AddingIngredients (const ReadyToEat)
Pure : (res : ty) -> SaladAction ty (state_fn res) state_fn
(>>=) : SaladAction a state1 state2_fn
-> ((res : a) -> SaladAction b (state2_fn res) state3_fn)
-> SaladAction b state1 state3_fn
emptyBowl : SaladBowl
emptyBowl = MkSaladBowl False False False
prepSalad1 : SaladAction SaladBowl Initial (const ReadyToEat)
prepSalad1 = do
(b1 ** _) <- AddTomato emptyBowl
(b2 ** _) <- AddLettuce b1
(b3 ** _) <- AddCucumber b2
MixItUp b3
还有一些编译器应该报错的反例程序:
BAD : SaladAction SaladBowl Initial (const ReadyToEat)
BAD = do
(b1 ** _) <- AddTomato emptyBowl
(b2 ** _) <- AddTomato emptyBowl
(b3 ** _) <- AddLettuce b2
(b4 ** _) <- AddCucumber b3
MixItUp b4
BAD' : SaladAction SaladBowl Initial (const ReadyToEat)
BAD' = do
(b1 ** _) <- AddTomato emptyBowl
MixItUp b1
我希望最终的“成分”是总和,而不是布尔值 (data Lettuce = Romaine | Iceberg | Butterhead
),并且拥有更强大的语义,例如“您必须先添加生菜或菠菜,但不能同时添加两者”。
实际上,我感到非常迷茫,以至于我想象我的上述代码已经完全走错了方向...如何构建这个FSM (PDA?)来防止出现糟糕的程序?我特别想使用Haskell,也许使用Indexed Monads?
AddLettuce :: ... -> SaladAction (bowl ** HasIngredient lettuce bowl) st (const AddingIngredients)
(其他配料同理)中对st
加入适当的约束条件——即lettuce st = False
。 - user2407038