Core.Program.Unlift

Contents

Description

The Program monad is an instance of MonadIO, which makes sense; it's just a wrapper around doing IO and you call it using execute from the top-level main action that is the entrypoint to any program. So when you need to actually do some I/O or interact with other major libraries in the Haskell ecosystem, you need to get back to IO and you use liftIO to do it:

main :: IO ()
main = execute $do -- now in the Program monad write "Hello there" liftIO$ do
-- now something in IO
compileSourceCode source

write "Finished"


and this is a perfectly reasonable pattern.

Sometimes, however, you want to get to the Program monad from there, and that's tricky; you can't just execute a new program (and don't try: we've already initialized output and logging channels, signal handlers, your application context, etc).

main :: IO ()
main = execute $do -- now in the Program monad write "Hello there" liftIO$ do
-- now something in IO
-- log that we're starting compile      ... FIXME how???
result <- compileSourceCode source
case result of
Left err     -> -- debug the error  ... FIXME how???

write "Finished"


We have a problem, because what we'd like to do is use, say, debug to log the compiler error, but we have no way to unlift back out of IO to get to the Program monad.

To workaround this, we offer withContext. It gives you a function that you can then use within your lifted IO to run a (sub)Program action:

main :: IO ()
main = execute $do -- now in the Program monad write "Hello there" withContext$ \runProgram -> do
-- now lifted to IO

runProgram $do -- now "unlifted" back to Program monad! event "Starting compile..." event "Nah. Changed our minds" event "Ok, fine, compile the thing" -- more IO result <- compileSourceCode source case result of Right object -> linkObjectCode object Left err -> runProgram (debugS err) -- back in Program monad write "Finished"  Sometimes Haskell type inference can give you trouble because it tends to assume you mean what you say with the last statement of do-notation block. If you've got the type wrong you'll get an error, but in an odd place, probably at the top where you have the lambda. This can be confusing. If you're having trouble with the types try putting return () at the end of your subprogram. Synopsis # Unlifting withContext :: ((forall β. Program τ β -> IO β) -> IO α) -> Program τ α Source # This gives you a function that you can use within your lifted IO actions to return to the Program monad. The type signature of this function is a bit involved, but the example below shows that the lambda gives you a function as its argument (we recommend you name it runProgram for consistency) which gives you a way to run a subprogram, be that a single action like writing to terminal or logging, or a larger action in a do-notation block: main :: IO () main = execute$ do
withContext $\runProgram -> do -- in IO monad, lifted -- (just as if you had used liftIO) ... runProgram$ do
-- now unlifted, back to Program monad

...


Think of this as liftIO with an escape hatch.

This function is named withContext because it is a convenience around the following pattern:

    context <- getContext
liftIO $do ... subProgram context$ do

Get the internal Context of the running Program. There is ordinarily no reason to use this; to access your top-level application data τ within the Context use getApplicationState.
Run a subprogram from within a lifted IO block.