Skip to content

Commit

Permalink
Merge pull request #168 from istathar/change-program
Browse files Browse the repository at this point in the history
Enable changing application state type in sub programs
  • Loading branch information
istathar authored Feb 7, 2023
2 parents 24a6925 + 168ab6a commit c00fa0e
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 4 deletions.
2 changes: 1 addition & 1 deletion core-program/core-program.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack

name: core-program
version: 0.6.2.3
version: 0.6.3.0
synopsis: Opinionated Haskell Interoperability
description: A library to help build command-line programs, both tools and
longer-running daemons.
Expand Down
3 changes: 2 additions & 1 deletion core-program/lib/Core/Program/Context.hs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ reason to use this; to access your top-level application data @τ@ within the
getContext :: Program τ (Context τ)
getContext = do
context <- ask
return context
pure context
{-# INLINABLE getContext #-}

{- |
Run a subprogram from within a lifted @IO@ block.
Expand Down
53 changes: 53 additions & 0 deletions core-program/lib/Core/Program/Execute.hs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ module Core.Program.Execute
, getConsoleWidth
, getApplicationState
, setApplicationState
, changeProgram

-- * Useful actions
, outputEntire
Expand Down Expand Up @@ -117,6 +118,7 @@ import Control.Concurrent.MVar
( MVar
, modifyMVar_
, newEmptyMVar
, newMVar
, putMVar
, readMVar
, tryPutMVar
Expand Down Expand Up @@ -582,6 +584,57 @@ setApplicationState user = do
let v = applicationDataFrom context
modifyMVar_ v (\_ -> pure user)

{- |
Sometimes you need to change the type of the application state from what is
present at the top-level when the program starts.
While the original intent of providing an initial value of type @τ@ to
'configure' was that your application state would be available at startup, an
alternative pattern is to form the application state as the first actions that
your program takes in the 'Program' @τ@ monad. This is especially common if you
are processing command-line options. In that case, you may find it useful to
initialize the program at type 'None', say, and then change to the 'Program'
@υ@ monad you intend to run through the actual program with once the full
settings object is available. You can do that using this function.
For example:
@
main :: 'IO' ()
main = do
context <- 'Core.Program.Execute.configure' \"1.0\" 'None' ('simpleConfig' ...)
'Core.Program.Execute.executeWith' context program1
program1 :: 'Program' 'None' ()
program1 = do
-- do things to form top-level application state
let settings =
Settings
{ ...
}
'changeProgram' settings program2
program2 :: 'Program' Settings ()
program2 = do
-- now carry on with application logic
...
@
This allows your code do do 'queryOptionValue' and the like in @program1@ and
then, once all the settings and initialization is complete, you can switch to
the actual type you intend to run at in @program2@.
@since 0.6.3
-}
changeProgram :: υ -> Program υ α -> Program τ α
changeProgram user' program = do
context1 <- ask
liftIO $ do
u <- newMVar user'
let context2 = context1 {applicationDataFrom = u}
subProgram context2 program

{- |
Write the supplied @Bytes@ to the given @Handle@. Note that in contrast to
'write' we don't output a trailing newline.
Expand Down
2 changes: 1 addition & 1 deletion core-program/package.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: core-program
version: 0.6.2.3
version: 0.6.3.0
synopsis: Opinionated Haskell Interoperability
description: |
A library to help build command-line programs, both tools and
Expand Down
20 changes: 19 additions & 1 deletion tests/CheckProgramMonad.hs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,24 @@ checkProgramMonad = do
user2 <- runProgram getApplicationState -- unlift!
user2 `shouldBe` user1

it "type of application state can be changed" $ do
context <- configure "0.1" None blankConfig
executeWith context $ do
user1 <- getApplicationState
liftIO $ do
user1 `shouldBe` None

let truth = True
changeProgram truth $ do
user' <- getApplicationState
liftIO $ do
user' `shouldBe` True

user2 <- getApplicationState
liftIO $ do
user2 `shouldBe` None


it "thrown Exceptions can be caught" $ do
context <- configure "0.1" None blankConfig
(subProgram context (throw Boom)) `shouldThrow` boom
Expand Down Expand Up @@ -110,4 +128,4 @@ checkProgramMonad = do

describe "Package metadata" $ do
it "the source location is accessible" $ do
render 80 __LOCATION__ `shouldBe` "tests/CheckProgramMonad.hs:113"
render 80 __LOCATION__ `shouldBe` "tests/CheckProgramMonad.hs:131"

0 comments on commit c00fa0e

Please sign in to comment.