我有以下数据结构和函数:
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
其中我们将具有CCD_ 6。
然后整个代码应读取为
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表示法只是用>>=
绑定和lambdas重写<-
绑定的语法糖。如果你能和一个一起写,你就可以和另一个一起。所以,如果你认为你可以用lambdas写这篇文章,我鼓励你这样做。然后,重写它,用do表示法,你就会学到一些东西。但我怀疑你会遇到同样的障碍,因为正如我所说,使用lambdas而不是do表示法实际上并没有什么特别之处。
我很难说你想写什么,因为你没有为getFresh
提供类型签名。有点令人费解的是,这个函数将中的状态作为直接参数,而不是像程序的其他部分那样参与state monad。我建议重写它以具有签名
getFresh :: String -> State [String] String
getFresh m = do {...}
当然,您必须更改实现,为此,我建议您研究get
和put
操作。但是,在进行了此更改后,您的freshNodesS
函数将不必对状态参数进行任何手动线程处理,因为它将完全由状态机器处理,如预期的那样:
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
通过这种方式,您可以清楚地表明,除了共享相同的状态之外,这三个操作都不相互依赖,并且您不需要为作用不大的变量命名。