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一个值,并将其添加到某个变量中。否则,我不会向变量添加任何内容,只是将其推入堆栈。

我怎样才能做到这种行为?

在Haskell中,每个if都需要一个else,因为一切都只是一个值。此外,由于do表示法在if语句中丢失,因此不能执行if ... then p <- pop ...,因此需要使用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

这里是最小的修复:

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块属于相同单元的某个单元值类型;特别是整个CCD_ 15块的类型,如果该CCD_

另请参阅:

  • do符号标记信息
  • do符号解释,色彩鲜艳

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

这一点已经被其他答案和正确版本的代码所解决。我将在这里详细说明。所有let变量都是不可变的,包括在monad内部。可以在monad中重新定义一个变量,但这只是在相同的monadic操作后面的代码中隐藏原始变量。someValue的重新分配发生在嵌套的一元操作中(有一个新的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

当然,这对于纯值来说毫无意义,因为必须始终提供一个值。但对于有条件执行的一元操作(产生(),或者我们有同样的问题:在条件错误的情况下,返回值是多少?(来说,这确实是有意义的。

仅就安全性而言,when定义如下:

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

因此pure ()充当"null"的一元操作。

哈斯克林快乐!

最新更新