MrKickkiller
MrKickkiller

Reputation: 521

Perform an IO action in a non IO environment

I've gotten stuck on the following predicament.

evalAExpr :: AExpr -> Env -> Int
evalAExpr (Var x) env = actAccordingly (getValueOfBinding env x)
  where
    actAccordingly (Left int) = int
    actAccordingly (Right stmt) = eval stmt env

eval :: Stmt -> Env ->  IO Env`

getValueOfBinding :: Env -> String -> Either Integer Stmt

So what needs to happen is that with the Stmt x gotten from the Either Monad, it should evaluate x (which needs to happen in an IO, because of other processes) into a new Environment, after which I can return the correct value from the environment. However, from my limited knowledge of Haskell I can't ever get out of an IO.

Eg: givetheCorrectThing nameOfTheValueInTheEnv (eval stmt env) would never work with the current code, since all evalAExpr functions (yes there are more than just 1) return an Env atm. If I would have to change that, it would result in adding in ALL of the evaluation do and return statements. > 50-60 occurences. For sure. That would make code so messy to look at. The reason I need IO is because this evaluation will have to go (at some point) and collect data from an mBot (http://makeblock.com/mbot-stem-educational-robot-kit-for-kids/), aswell as collect data from the keyboard (done via getLine)

Would any of you have a way to rework this code so that I can keep it neat and organised?

If more code is needed: Feel free to ping / mention me. I will provide the requested lines of code.

More code as requested:

-- Code that shows how EvalAExpr is used.

-- Relational operators
evalBExpr (RBinary Greater a b) env = evalAExpr a env > evalAExpr b env
evalBExpr (RBinary Less a b)    env = evalAExpr a env < evalAExpr b env
evalBExpr (RBinary Equal a b)   env = evalAExpr a env == evalAExpr b env

Upvotes: 1

Views: 175

Answers (2)

leftaroundabout
leftaroundabout

Reputation: 120741

from my limited knowledge of Haskell I can't ever get out of an IO

Correct! And for good reasons: with the current signature, anybody using your function knows that this really is just a pure, referentially transparent calculation, so they can be accordingly careless with it (lazyness etc.). If you were allowed to just put in some IO without making this explicit through the signature, you might easily break everybody's code!

Of course, if you've only just noticed that you need to do IO, then you are indeed in a bit of a dilemma. There's no good way for you to get around changing all the signatures now.

However, you could have prevented this by using a type synonym in the first place: if you'd had originally

type Evaluation a = Identity a

-- ... lots of functions with an `Evaluation` result ...

evalAExpr :: AExpr -> Env -> Evaluation Int
evalAExpr (Var x) env = actAccordingly (getValueOfBinding env x)
  where
    actAccordingly (Left int) = return int
    actAccordingly (Right stmt) = someThingThatDidn'tYetRequireIO

then, upon noticing that you need IO after all, you could have just changed it to

type Evaluation a = IO a

{- same as before:
evalAExpr :: AExpr -> Env -> Evaluation Int
evalAExpr (Var x) env = actAccordingly (getValueOfBinding env x)
  where
    actAccordingly (Left int) = return int -}
    actAccordingly (Right stmt) = eval stmt env

In a big project, the Evaluation monad would then typically be a monad transformer stack, to which you'd just add an extra layer for the IO underneath.


If anybody reminds me about the function that shall not be named, I shall whack them about the head with a nuclear missile.

As dfeuer remarks, a newtype may be better.

Upvotes: 2

ErikR
ErikR

Reputation: 52049

You have to write a monadic version of evalAExpr

We'll call the new version evalAExprIO:

evalAExprIO :: AExpr -> Env -> IO Int
 evalAExpr (Var x) env = actAccordingly (getValueOfBinding env x)
  where
    actAccordingly (Left int) = return int
    actAccordingly (Right stmt) = eval stmt env

There is really only one significant change -- in the Left case the function actAccordingly uses return int instead of just int.

The type signature for evalAExprIO has changed, but this is something that GHC can infer so it's not something you have to know how to change.

Update

Change:

evalBExpr (RBinary Greater a b) env = evalAExpr a env > evalAExpr b env

to:

evalBexpr (RBinary Greater a b) env = liftM2 (>) (evalAExpr a env) (evalExpr b env)

(import Control.Monad for liftM2.)

Upvotes: 1

Related Questions