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

在Haskell中使用let-in-do块中的状态,haskell,state-monad,Haskell,State Monad,我有以下数据结构和功能: 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

我有以下数据结构和功能:

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')
这是有效的。

状态单子的全部要点是自动传递状态,而不必显式地这样做。几乎没有函数应该获取s作为参数,或者返回它

比如说,

let m' = getFresh m s
是可疑的,应该读一下

m' <- getFresh m
请注意,没有提到s或s。这应该类似于命令式代码,其中可变状态变量由每个函数调用修改,即使代码没有明确提到这一点

现在,在getFresh中,您将不得不与美国打交道,因为这是无法避免的。如果您的State monad是标准monad,则可以使用get和put访问State。你可能需要像这样的东西

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'
状态单子的全部要点是自动传递状态,而不必显式地这样做。几乎没有函数应该获取s作为参数,或者返回它

比如说,

let m' = getFresh m s
是可疑的,应该读一下

m' <- getFresh m
请注意,没有提到s或s。这应该类似于命令式代码,其中可变状态变量由每个函数调用修改,即使代码没有明确提到这一点

现在,在getFresh中,您将不得不与美国打交道,因为这是无法避免的。如果您的State monad是标准monad,则可以使用get和put访问State。你可能需要像这样的东西

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一个类型签名。这个函数将状态作为直接参数,而不是像程序的其余部分那样参与状态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

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

do表示法只是语法上的糖重写>=binds和lambdas。如果你能用一个来写,你就可以用另一个来写。所以,如果你认为你可以用lambdas写这篇文章,我鼓励你这样做。然后,重写它,使用do符号,您将学到一些东西。但我怀疑您会遇到同样的障碍,因为正如我所说的,使用lambdas代替do符号实际上没有什么特别之处

我很难说你想写什么,因为你没有给getFresh一个类型签名。这个函数将状态作为直接参数,而不是像程序的其余部分那样参与状态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
通过这种方式,您可以清楚地了解到,除了共享相同的状态之外,这三个操作都不相互依赖,并且您不需要为不起多大作用的变量命名