问题描述
我具有以下数据结构和功能:
data BTree a = BLeaf | BNode (BTree a) a (BTree a) deriving (Show,Eq)
freshNodesS :: BTree String -> State [String] (BTree String)
freshNodesS BLeaf = return BLeaf
freshNodesS (BNode l m r) = do l' <- freshNodesS l
let m' = getFresh m s
let s' = m : s
r' <- freshNodesS r s'
return (BNode l' m' r')
有一个问题,我实际上要使用freshNodesS l
的状态,该状态应提供(BTree String,[String])
的输出,但是在do块中我不能使用(l',s) <- freshNodesS l
,我看到的唯一选择是将所有内容都放在lambda函数中。
但是有办法我仍然可以使用do表示法吗?
在@chi说完之后,我这样做了:
freshNodesS BLeaf = return BLeaf
freshNodesS (BNode l m r) = do l' <- freshNodesS l
m' <- getFreshS m
r' <- freshNodesS r
return (BNode l' m' r')
getFreshS :: String -> State [String] String
getFreshS x = state $ (\s -> let newx = getFresh x s in (newx,newx: s))
那行得通。
解决方法
State
monad的全部要点是自动传递状态,而无需您明确地进行操作。几乎没有函数应该将s
作为参数或返回它。
例如,
let m' = getFresh m s
可疑,可能应该阅读
m' <- getFresh m
我们将拥有getFresh :: String -> State [String] String
的地方。
整个代码应读为
do l' <- freshNodesS l
m' <- getFresh m
r' <- freshNodesS r
return (BNode l' m' r')
请注意,从未提及过s
或s'
。这应该看起来像命令式代码,其中每个函数调用都会修改一个可变状态变量,即使该代码没有明确提及。
现在,在getFresh
中,您将不得不处理州,因为没有办法解决。如果您的State
monad是标准的monad,则可以使用get
和put
访问状态。您可能需要类似
getFresh :: String -> State [String] String
getFresh m = do
s <- get -- read the current state
let m' = ... -- compute a fresh name m'
let s' = m' : s -- mark m' as used
put s' -- write the current state
return m'
,
do-notation只是句法糖重写<-
绑定与>>=
绑定和lambda。如果可以用一个书写,则可以用另一个书写。因此,如果您认为可以使用lambda编写代码,则建议您这样做。然后,将其重写以使用do-notation,您将学到一些东西。但是我怀疑您会遇到同样的绊脚石,因为正如我说的那样,使用lambda代替do-notation实际上并没有什么特别之处。
我很难说出您打算写什么,因为您没有为getFresh
提供类型签名。令人费解的是,此函数将状态作为直接参数,而不是像程序的其余部分一样参与State monad。我建议将其重写以具有签名
getFresh :: String -> State [String] String
getFresh m = do {...}
您当然必须更改实现,我建议您查看get
和put
操作。但是进行了此更改后,您的freshNodesS
函数将无需执行任何手动穿插state参数的操作,因为它将完全由State机制按预期方式处理:
freshNodesS (BNode l m r) = do
l' <- freshNodesS l
m' <- getFresh m
r' <- freshNodesS r
return (BNode l' m' r')
或者,您可以改用应用样式编写此代码:
freshNodesS (BNode l m r) =
BNode <$> freshNodesS l <*> getFresh m <*> freshNodesS r
这样,您就可以清楚地知道,除了共享相同的状态外,这三个操作都不相互依赖,并且不需要为执行不多的变量命名。