Haskell 中的有条件状态更改

Conditional change of State in Haskell

我不知道如何在 Haskell 中对 State Monad 进行有条件的更改。 假设,我在 State Monad 上有一个堆栈。

import Control.Monad.State

push :: Int -> State [Int] ()
push x = state $ \xs -> ((), x : xs)

pop :: State [Int] Int
pop = state $ \(x : xs) -> (x, xs)

然后,我想写一个函数来改变它。

someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do 
  let someValue = x * x
  if b then
    p <- pop
    someValue = someValue + p
  push $ someValue

例如,我想取一个布尔值b和一个值x。如果 bTrue(例如,这可能是我的堆栈不为空的条件),我想 pop 我的堆栈中的一个值并将其添加到某个变量。否则,我不向变量添加任何内容,只是将其压入堆栈。

我怎样才能实现这种行为?

这是最小的修复:

someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do 
  let someValue = x * x
  if b then
    do { p <- pop
       ; push $ someValue + p }
  else
    push $ someValue

if 表达式的两个分支必须具有相同的类型,并且在 do 符号内部必须是属于与整体 do 堵塞;特别是整个 do 块的类型,如果 if 表达式是其中的最后一个。

另请参阅:

在 Haskell 中,每个 if 都需要一个 else,因为一切都只是一个值。此外,您不能执行 if ... then p <- pop ...,因为 do 符号在 if 语句中丢失,因此您需要使用 if ... then do p <- pop ....

重新启动它
import Control.Monad.State

push :: a -> State [a] ()
push x = state $ \xs -> ((), x : xs)

pop :: State [a] a
pop = state $ \(x : xs) -> (x, xs)

someFunc :: (Num a) => Bool -> a -> State [a] ()
someFunc b x = do
  let baseValue = x * x
  someValue <- if b then do
        p <- pop
        return $ baseValue + p
      else do
        return baseValue
  push $ someValue

除了您提出的问题之外,如何在没有 else 子句的情况下有条件地执行单子操作,您的代码还有另一个问题。您想在条件子句中重新分配一个 let 变量。

其他答案已经解决了这一点,并提供了正确的代码版本。我会在这里详细说明。所有 let 变量都是不可变的,包括内部 monad。您 可以 重新定义 monad 中的变量,但这只会隐藏原始变量,使其远离 same monadic 操作的代码。 someValue 的重新分配发生在嵌套的 monadic 操作中(有一个新的 do 块)并且在 if.

之外不可见

有条件地重新分配 let 变量基本上是不可能的。编译器需要在编译时知道引用了哪个值。做到这一点的方法是 return 来自条件的值然后分配变量,如@Aplet123 的答案,或者 运行 条件的两个分支中的动作对不同的值,如@Will Ness说。

回答所述问题:您可以使用 Control.Monad 中的 when 函数有条件地 运行 单子动作(没有 else 分支)。所以如果你的代码真的没有else分支,你可以这样写:

someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do 
  let someValue = x * x
  when b $ do
    p <- pop
    push $ someValue + p

当然这对纯值没有意义,因为必须始终提供一个值。但是对于有条件执行的 monadic 动作(产生 (),或者我们有同样的问题:在条件为假的情况下 return 值是多少?)确实有意义。

出于好奇,when定义如下:

when :: (Applicative f) => Bool -> f () -> f ()
when p s  = if p then s else pure ()

所以 pure () 作为 'null' 单子动作。

Haskelling 快乐!