Search code examples
haskellstate-monad

Haskell state monad different behaviour on even and odd function calls


I want to make a funciton that adds 5 on odd calls and subtracts 6 on even calls using state monads.

f 5 =  10
f 7 = 1
f 4 = 9
f 2 = -4

0 is even so f 5 adds 5. 1 is odd so f 7 subtracts 6 and so on.

What I have now:

data Parity = Even | Odd deriving (Show, Eq)

not' :: Parity -> Parity
not' Even = Odd
not' Odd = Even

isOdd :: Int -> State Parity Int
isOdd x = state $ \(p) -> (if p == Odd then x + 5 else x - 6, not' p)

g n = do isOdd n

apply n = runState (g n) Even

I tried writing it like that, but everytime 'apply' is used the state is not saved. It only add 5 because of the even at the end. How do I make it save the state and only initalize it once and not every time ?


Solution

  • This answer I wrote a few days ago may be helpful. Keeping it short, State s is just a convenient way of simulating "stateful functions" f :: a -> b as pure functions f :: (a,s) -> (b,s). To fit the monad framework though these are curried, so (roughly) of the form f :: a -> s -> (b,s).

    The type State s b is roughly s -> (b,s), which can be read as "a computation that returns a value b and a final state s and that requires an initial state s to be run". A monadic function a -> State s b is therefore a function that takes an input a, and that can be run given an initial state s, to produce a value b and a final state s.

    Your function isOdd is,

    isOdd x :: Int -> State Parity Int
    isOdd x = state $ \p -> (if p == Odd then x + 5 else x - 6, not' p)
    

    which is roughly,

    isOdd' x :: Int -> Parity -> (Int,Parity)
    isOdd' x p = (if p == Odd then x + 5 else x - 6, not' p)
    

    And your call,

     apply n = runState (isOdd n) Even 
    

    is roughly,

     apply' n = isOdd' x Even
    

    That's all. You are essentially calculating

     apply' n = --definition of apply'
                isOdd' n Even
                -- definition of isOdd'
                (\x p -> (if p == Odd then x + 5 else x - 6, not' p)) n Even
                -- application to the arguments `n` and `Even` 
                = (if Even == Odd then n + 5 else n - 6, not' Even)
                -- simplifying
                = (n - 6, Odd)
    

    so,

    apply' n = (n - 6, Odd)
    

    Here is an example of how to sequence your function properly,

    f :: Int -> State Parity Int
    f n = isOdd n >>= (\x -> isOdd x) 
    

    or equivalently

    f :: Int -> State Parity Int
    f n = do x <- isOdd n
             isOdd x
    

    When you run it via e.g. apply n = runState (f n) Even you are first running isOdd n Even, to obtain a result m and a new final state which will be False, and then running isOdd m False.