Reputation: 13
I don't know how I can make a conditional change to State Monad in Haskell. Suppose, I have a stack on 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)
And with that, I want to write a function changing it.
someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do
let someValue = x * x
if b then
p <- pop
someValue = someValue + p
push $ someValue
For example, I want to take a bool b
and a value x
. And if b
is True
(for example, it could be a condition that my stack is not empty) I want to pop
a value from my stack and add it to some variable. Otherwise, I don't add anything to the variable and just push it into the stack.
How can I achieve this kind of behaviour?
Upvotes: 1
Views: 652
Reputation: 35560
In Haskell, every if
needs an else
, since everything is just a value. Also, you can't do if ... then p <- pop ...
since the do
notation is lost in an if
statement, so you need to restart it with 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
Upvotes: 1
Reputation: 2515
In addition to the question you asked, how to conditionally execute a monadic action without an else
clause, there is another problem with your code. You want to reassign a let-variable inside a conditional clause.
This point has been addressed by the other answers, with correct versions of the code. I will elaborate here. All let-variables are immutable, including inside monads. You can redefine a variable in a monad, but that merely hides the original variable from code further down the same monadic action. The reassignment of someValue
takes place inside a nested monadic action (there's a new do
block) and is not visible outside of the if
.
Conditionally reassigning a let-variable is fundamentally impossible. The compiler needs to know at compile time which value is being referred to. Ways to do this are either to return
a value from the conditional and then assign the variable, like @Aplet123's answer, or run the action in both branches of the conditional on different values, as @Will Ness said.
In reply to the question as stated: you can conditionally run a monadic action (with no else
branch) using the when
function from Control.Monad
. So if your code really had no else
branch, you could write it like this:
someFunc :: Bool -> Int -> State [Int] ()
someFunc b x = do
let someValue = x * x
when b $ do
p <- pop
push $ someValue + p
Of course this makes no sense for pure values, since a value must always be provided. But it does make sense for a monadic action (yielding ()
, or we have the same problem: what is the return value in case of a false conditional?) to be conditionally executed.
Just for curiousity, when
is defined as follows:
when :: (Applicative f) => Bool -> f () -> f ()
when p s = if p then s else pure ()
So pure ()
serves as the 'null' monadic action.
Happy Haskelling!
Upvotes: 1
Reputation: 71119
Here's the smallest fix:
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
Both branches of the if
expression must have the same type, and inside the do
notation that must be some monadic value type belonging to the same monad as does the overall do
block; specifically the type of the overall do
block if that if
expression is the last one in it.
See also:
Upvotes: 1