在Haskell中的let in a do块中使用状态



我有以下数据结构和函数:

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))

这起到了作用。

Statemonad的全部目的是自动传递状态,而不必显式地这样做。几乎没有一个函数应该将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')

请注意,从未提及ss'。这应该看起来像命令式代码,其中每个函数调用都会修改可变状态变量,即使代码没有明确提到这一点。

现在,在getFresh中,你将不得不与各州打交道,因为这是没有办法的。如果您的Statemonad是标准monad,则可以使用getput访问状态。你可能需要类似的东西

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 {...}

当然,您必须更改实现,为此,我建议您研究getput操作。但是,在进行了此更改后,您的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

通过这种方式,您可以清楚地表明,除了共享相同的状态之外,这三个操作都不相互依赖,并且您不需要为作用不大的变量命名。

最新更新