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:
- Why can’t I write nullary functions in the State monad?
- Why can’t I declare the type my workaround function actually has?
The problem is that you don’t write type signatures for your functions, and the monomorphism restriction applies.
When you write:
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:
Here, we define
piwithout a type, so GHC infers the typeFractional a => a, i.e. “any typea, as long as it can be treated like a fraction.” However, this type is problematic, because it means thatpiis not a constant, even though it looks like it is. Why? Because the value ofpiwill be re-computed depending on what type we want it to be.If we have
(2::Double) + pi,piwill be aDouble. If we have(3::Float) + pi,piwill be aFloat. Each timepiis used, it therefore has to be re-computed (because we can’t store alternative versions ofpifor all possible fractional types, can we?). This is fine for the simple literal3.14, but what if we wanted more decimals ofpiand used a fancy algorithm that calculated it? We wouldn’t want it to be recomputed every timepiis 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,
piwould get thedefaulttype ofDouble. You can change the default numeric types if you want, using thedefaultkeyword:In your case, however, you are getting the inferred signature:
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 form. 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:
… but you will have to add the
FlexibleContextsHaskell language extension for it to work, by adding this at the top of your file:Or, you can specify explicitly which state monad you want:
You can also disable the monomorphism restriction, by adding the extension for that; it is much better to add type signatures, however.
PS. If you have a function that takes your state as a parameter, you can use:
You should also look into using Lenses with State monads, which lets you write very clean code for implicit state passing.