haskellstatemonadsmonomorphism-restriction

Using the state monad to hide explicit state


I'm trying to write a little game in Haskell, and there's a fair amount of state necessary to pass around. I want to try hiding the state with the State monad

Now I've run into a problem: functions which take the state and an argument were easy to write to work in the state monad. But there are also functions that just take the state as argument (and return a modified state, or possibly something else).

In one part of my code, I have this line:

let player = getCurrentPlayer state

I would like it to not take state, and instead write

player <- getCurrentPlayerM

currently, its implementation looks like this

getCurrentPlayer gameState = 
  (players gameState) ! (on_turn gameState)

and it seemed simple enough to make it work in the State monad by writing it like this:

getCurrentPlayerM = do state <- get
                       return (players state ! on_turn state)

However, that provokes complaints from ghc! No instance for (MonadState GameState m0) arising from a use of `get', it says. I had already rewritten a very similar function, except that wasn't nullary in its State monad form, so on a hunch, I rewrote it like this:

getCurrentPlayerM _ = do state <- get
                         return (players state ! on_turn state)

And sure enough, it works! But of course I have to call it as getCurrentPlayerM (), and I feel a little silly doing that. Passing in an argument was what I wanted to avoid in the first place!

An additional surprise: looking at its type in ghci I get

getCurrentPlayerM :: MonadState GameState m => t -> m P.Player

but if I try to set that explicitly in my code, I get another error: "Non type-variable argument in the constraint MonadState GameState m" and an offer of a language extension to permit it. I suppose it's because my GameState is a type and not a typeclass, but why it's accepted in practice but not when I try to be explicit about it I'm more confused about.

So to sum up:

  1. Why can't I write nullary functions in the State monad?
  2. Why can't I declare the type my workaround function actually has?

Solution

  • The problem is that you don't write type signatures for your functions, and the monomorphism restriction applies.

    When you write:

    getCurrentPlayerM = ...
    

    you are writing a top-level unary constrained value definition without a type declaration, so the Haskell compiler will try to infer a type for the definition. However, the monomorphism restriction (Literally: single-shape restriction) states that all top-level definitions with inferred type constraints have to resolve to concrete types, i.e. they mustn't be polymorphic.


    To explain what I mean, take this simpler example:

    pi = 3.14
    

    Here, we define pi without a type, so GHC infers the type Fractional a => a, i.e. "any type a, as long as it can be treated like a fraction." However, this type is problematic, because it means that pi is not a constant, even though it looks like it is. Why? Because the value of pi will be re-computed depending on what type we want it to be.

    If we have (2::Double) + pi, pi will be a Double. If we have (3::Float) + pi, pi will be a Float. Each time pi is used, it therefore has to be re-computed (because we can't store alternative versions of pi for all possible fractional types, can we?). This is fine for the simple literal 3.14, but what if we wanted more decimals of pi and used a fancy algorithm that calculated it? We wouldn't want it to be recomputed every time pi is used then, would we?

    This is why the Haskell Report states that top-level unary type-constrained definitions must have a single type (monomorphic), to avoid this problem. In this case, pi would get the default type of Double. You can change the default numeric types if you want, using the default keyword:

    default (Int, Float)
    
    pi = 3.14 -- pi will now be Float
    

    In your case, however, you are getting the inferred signature:

    getCurrentPlayerM :: MonadState GameState m => m P.Player
    

    It means: "For any state monad that stores GameStates, retrieve a player." However, because the monomorphism restriction applies, Haskell is forced to try to make this type non-polymorphic, by choosing a concrete type for m. However, it can't find one, because there is no type defaulting for state monads like there is for numbers, so it gives up.

    You either want to give your function an explicit type signature:

    getCurrentPlayerM :: MonadState GameState m => m P.Player
    

    ... but you will have to add the FlexibleContexts Haskell language extension for it to work, by adding this at the top of your file:

    {-# LANGUAGE FlexibleContexts #-}
    

    Or, you can specify explicitly which state monad you want:

    getCurrentPlayerM :: State GameState P.Player
    

    You can also disable the monomorphism restriction, by adding the extension for that; it is much better to add type signatures, however.

    {-# LANGUAGE NoMonomorphismRestriction #-}
    

    PS. If you have a function that takes your state as a parameter, you can use:

    value <- gets getCurrentPlayer
    

    You should also look into using Lenses with State monads, which lets you write very clean code for implicit state passing.