Stephane Rolland
Stephane Rolland

Reputation: 39906

Is it possible to use IO inside State monad, without using StateT and ST

In the code below I manage a game, which owns a list of links. At each step of the game, I change the game state updating the list of links modified.

As I am learning the State monad, I was trying to apply the State monad technique to this use case.

Nonetheless, at each turn, I need to get a piece of info from IO, using getLine

this gives such a code

{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
import Control.Monad
import Control.Monad.State.Strict
import qualified Data.List as List
import qualified Control.Monad.IO.Class as IOClass

type Node = Int 
type Link = (Node,Node)
type Links = [Link]
type Gateway = Node
type Gateways = [Gateway]

data Game = Game { nbNodes :: Int, links :: Links, gateways :: Gateways }

computeNextTurn :: State Game Link
computeNextTurn = do
    g <- get
    inputLine <- IOClass.liftIO getLine -- this line causes problem
    let node = read inputLine :: Int
    let game@(Game _ ls gs) = g
    let linkToSever = computeLinkToSever game node
    let ls' = List.delete linkToSever ls
    let game' = game{links = ls'}
    put game'
    return linkToSever

computeAllTurns :: State Game Links
computeAllTurns = do
            linkToSever <- computeNextTurn
            nextGames <- computeAllTurns
            return (linkToSever : nextGames)

computeLinkToSever :: Game -> Node -> Link
computeLinkToSever _ _ = (0,1) -- just a dummy value
        -- this function doesnt compute really anything for the moment
        -- but it will depend on the value of node i got from IO

However I get an error at compilation:

No instance for (MonadIO Data.Functor.Identity.Identity) arising from a use of liftIO

and I get the same style of error, if I try to use liftM and lift.

I have read some questions that are suggesting StateT and ST, which I don't grasp yet.

I am wondering if my current techique with a simple State is doomed to fail, and that indeed I can not use State, but StateT / ST ?

Or is there a possible operation to simply get the value from getLine, inside the State monad ?

Upvotes: 3

Views: 1229

Answers (1)

Cactus
Cactus

Reputation: 27626

As @bheklilr said in his comment, you can't use IO from State. The reason for that, basically, is that State (which is just shorthand for StateT over Identity) is no magic, so it's not going to be able to use anything more than

  • What you can already do in its base monad, Identity
  • The new operations provided by State itself

However, that first point also hints at the solution: if you change the base monad from Identity to some other monad m, then you get the capability to use the effects provided by m. In your case, by setting m to IO, you're good to go.

Note that if you have parts of your computation that don't need to do IO, but require access to your state, you can still express this fact by making their type something like

foo :: (Monad m) => Bar -> StateT Game m Baz

You can then compose foo with computations in StateT Game IO, but its type also makes it apparent that it can't possibly do any IO (or anything else base monad-specific).

You also mentioned ST in your question as possible solution. ST is not a monad transformer and thus doesn't allow you to import effects from some base monad.

Upvotes: 4

Related Questions