lexi-lambda / freer-simple

A friendly effect system for Haskell
https://hackage.haskell.org/package/freer-simple
BSD 3-Clause "New" or "Revised" License
227 stars 19 forks source link

Wrapping an existing library - How to maintain state? #26

Closed NickSeagull closed 5 years ago

NickSeagull commented 5 years ago

Hello! Thanks for this awesome library :pray: :blush:

I'm trying to make an effect to represent actions from hint which defines an Interpreter monad that it is meant to be run using the function

runInterpreter :: Interpreter a -> IO (Either InterpreterError a)

I've created my own Interpreter (lets call it MyInterpreter) effect to provide a slightly better API (Text instead of String, newtypes where needed, etc...)

And then wrote a function to translate from my effect to the monad from the library:

asHint :: MyInterpreter a -> Interpreter a

My issue is the runInterpreter function from above, I don't want it to return Either, I prefer to use the Error effect, so I'm trying to implement a foo function for handling this:

foo
  :: forall effs a
  .  (Members '[IO, Error InterpreterError] effs)
  => Hint.Interpreter a
  -> Eff effs a
foo action = do
  result <- send (Hint.runInterpreter action)
  case result of
    Right x -> pure x
    Left e -> throwError e

So for example I could try to do

myProgram :: Member MyInterpreter effs => Eff effs ()
myProgram =
  runStatement "x = 42"
  runStatement "print x"

main = runM (handleError print (interpret foo myProgram))

The expected output would be 42, but what happens is that it fails saying that 42 is not in scope. When using the library directly (without effects) this works properly.

My suspicion is that interpret foo is running Hint.runInterpreter once for each of the values of the GADT specifying the effect, making it lose completely the context.

Is there a way of gathering all the Interpreter values, and running Hint.runInterpreter on all of those?

Thanks in advance, any help is appreciated :pray:

NickSeagull commented 5 years ago

Thanks to @lexi-lambda on IRC, I've managed to fix it by forcing Hint.Interpreter to be the last member of the effect list. After that it is just a matter of using interpretM, runM and finally running Hint.runInterpreter just once. 😁