我不知道如何在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
。如果b
是True
(例如,这可能是我的堆栈不为空的条件(,我想从堆栈中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"的一元操作。
哈斯克林快乐!