在 Haskell 中的 do 块中使用 let 中的状态
Using the state in a let in a do block in Haskell
我有以下数据结构和函数:
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
作为参数,或者 return 它。
例如,
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 是标准的,您可以使用 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,您将学到一些东西。但我怀疑你会 运行 陷入同样的绊脚石,因为正如我所说,使用 lambdas 而不是 do-notation.
实际上并没有什么特别之处
我很难说出你想写什么,因为你没有为 getFresh
提供类型签名。有点令人费解的是,此函数将状态作为直接参数,而不是像程序的其余部分那样参与状态 monad。我建议重写它以具有签名
getFresh :: String -> State [String] String
getFresh m = do {...}
您当然必须更改实现,为此我建议您查看 get
和 put
操作。但是进行此更改后,您的 freshNodesS
函数将不再需要对状态参数进行任何手动操作 threading-through,因为它将完全由状态机处理,如预期的那样:
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
这样你就清楚了 none 这三个操作除了共享相同的状态外还相互依赖,你不需要为不做太多事情的变量命名。
我有以下数据结构和函数:
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
作为参数,或者 return 它。
例如,
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 是标准的,您可以使用 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,您将学到一些东西。但我怀疑你会 运行 陷入同样的绊脚石,因为正如我所说,使用 lambdas 而不是 do-notation.
我很难说出你想写什么,因为你没有为 getFresh
提供类型签名。有点令人费解的是,此函数将状态作为直接参数,而不是像程序的其余部分那样参与状态 monad。我建议重写它以具有签名
getFresh :: String -> State [String] String
getFresh m = do {...}
您当然必须更改实现,为此我建议您查看 get
和 put
操作。但是进行此更改后,您的 freshNodesS
函数将不再需要对状态参数进行任何手动操作 threading-through,因为它将完全由状态机处理,如预期的那样:
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
这样你就清楚了 none 这三个操作除了共享相同的状态外还相互依赖,你不需要为不做太多事情的变量命名。