Undreren
Undreren

Reputation: 2871

The state monad and learnyouahaskell.com

I was reading Learn You a Haskell's guide on the state monad, but I had trouble understanding it since the stack example couldn't compile. In the guide, he used the following piece of code:

import Control.Monad.State  

type Stack = [Int]

pop :: State Stack Int  
pop = State $ \(x:xs) -> (x,xs)  

push :: Int -> State Stack ()  
push a = State $ \xs -> ((),a:xs) 

While I understand what it's supposed to do, it won't compile. I have no idea why. The error message is:

Stack.hs:6:7: Not in scope: data constructor `State'

Stack.hs:9:10: Not in scope: data constructor `State'

This makes no sense to me, since "State" is, to my knowledge, in fact a data constructor, defined as

newtype State s a = State { runState :: s -> (a,s) }

Is the guide "wrong", and if so, how do I fix it?

Upvotes: 14

Views: 3051

Answers (2)

krilitunris
krilitunris

Reputation: 61

The accepted answer already mentions to use the state function from Control.Monad.State rather than the State type. However, if you simply try the accepted answer in ghci with the associated mtl package loaded, it will still fail:

Prelude Control.Monad.State> push a = state $ \xs -> ((), a:xs)

<interactive>:5:1: error:
    • Non type-variable argument in the constraint: MonadState [a] m
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        push :: forall a (m :: * -> *). MonadState [a] m => a -> m ()

To resolve this, there are two options here:

  1. Either specify the type signature for the function (which you should anyways).
  2. Add the FlexibleContexts compiler extension, as mentioned in the error.

We can specify the type signature:

module LearnYouAHaskell where

import Control.Monad.State as State

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

The above compiles fine and functions as expected.

Or we can add the language extension so it can infer the function type.

{-# LANGUAGE FlexibleContexts #-}
module LearnYouAHaskell where

import Control.Monad.State as State

push a = State.state $ \xs -> ((), a:xs)

Which will also work fine.

Upvotes: 1

Vitus
Vitus

Reputation: 11922

As I mentioned in comments, you ought to use state instead of State.


The problem is that State is not standalone data type (or rather newtype), but it is the StateT transformer applied to Identity monad. Actually, it's defined as

type State s = StateT s Indentity

and because it's just type synonym, it cannot have State constructor. That's why Control.Monad.State uses state.

Upvotes: 20

Related Questions