| Safe Haskell | Safe-Inferred |
|---|---|
| Language | Haskell2010 |
Core.Program.Unlift
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 monadwrite"Hello there"liftIO$ do -- now something in IO source <- readFile "hello.c" compileSourceCode source -- back in Program monadwrite"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 monadwrite"Hello there"liftIO$ do -- now something in IO source <- readFile "hello.c" -- log that we're starting compile ... FIXME how??? result <- compileSourceCode source case result of Right object -> linkObjectCode object Left err -> -- debug the error ... FIXME how??? -- back in Program monadwrite"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 monadwrite"Hello there"withContext$ \runProgram -> do -- now lifted to IO source <- readFile "hello.c" runProgram $ do -- now "unlifted" back to Program monad!info"Starting compile..."info"Nah. Changed our minds"info"Ok, fine, compile the thing" -- more IO result <- compileSourceCode source case result ofRightobject -> linkObjectCode objectLefterr -> runProgram (debugSerr) -- back in Program monadwrite"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
- withContext :: ((forall β. Program τ β -> IO β) -> IO α) -> Program τ α
- getContext :: Program τ (Context τ)
- subProgram :: Context τ -> Program τ α -> IO α
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$ dowithContext$ \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 <-getContextliftIO $ do ...subProgramcontext $ do -- now in Program monad ...
Note there is a MonadUnliftIO instance which may be useful to you as well.
Internals
getContext :: Program τ (Context τ) Source #
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.