packages feed

essence-of-live-coding 0.2.4 → 0.2.5

raw patch · 16 files changed

+377/−111 lines, 16 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- LiveCoding: buildLiveProg :: Sensor a -> SF a b -> Actuator b -> LiveProgram IO
- LiveCoding: data (:~:) (a :: k) (b :: k) :: forall k. () => k -> k -> Type
- LiveCoding: data (:~~:) (a :: k1) (b :: k2) :: forall k1 k2. () => k1 -> k2 -> Type
- LiveCoding.Cell: buildLiveProg :: Sensor a -> SF a b -> Actuator b -> LiveProgram IO
- LiveCoding.GHCi: load :: Launchable m => LiveProgram m -> IO (LaunchedProgram m)
- LiveCoding.RuntimeIO.Launch: instance LiveCoding.RuntimeIO.Launch.Launchable (Control.Monad.Trans.State.Strict.StateT (LiveCoding.Handle.HandlingState GHC.Types.IO) GHC.Types.IO)
+ LiveCoding: buildProg :: Sensor a -> SF a b -> Actuator b -> LiveProgram IO
+ LiveCoding: data (a :: k1) :~~: (b :: k2)
+ LiveCoding: data Proxy (t :: k)
+ LiveCoding: liveMain :: Launchable m => LiveProgram m -> IO ()
+ LiveCoding.Cell: buildProg :: Sensor a -> SF a b -> Actuator b -> LiveProgram IO
+ LiveCoding.GHCi: launchAndSave :: Launchable m => LiveProgram m -> IO ()
+ LiveCoding.GHCi: possiblyLaunchedProgram :: Launchable m => Proxy m -> IO (Either SomeException (Maybe (LaunchedProgram m)))
+ LiveCoding.GHCi: proxyFromLiveProgram :: LiveProgram m -> Proxy m
+ LiveCoding.GHCi: stopStored :: Launchable m => Proxy m -> IO ()
+ LiveCoding.GHCi: sync :: Launchable m => LiveProgram m -> IO ()
+ LiveCoding.LiveProgram.Except: LiveProgramExcept :: CellExcept m () () e -> LiveProgramExcept m e
+ LiveCoding.LiveProgram.Except: [unLiveProgramExcept] :: LiveProgramExcept m e -> CellExcept m () () e
+ LiveCoding.LiveProgram.Except: foreverCLiveProgram :: (Data e, Monad m) => LiveProgramExcept m e -> LiveProgram m
+ LiveCoding.LiveProgram.Except: foreverELiveProgram :: (Data e, Monad m) => e -> LiveProgramExcept (ReaderT e m) e -> LiveProgram m
+ LiveCoding.LiveProgram.Except: instance GHC.Base.Monad m => GHC.Base.Applicative (LiveCoding.LiveProgram.Except.LiveProgramExcept m)
+ LiveCoding.LiveProgram.Except: instance GHC.Base.Monad m => GHC.Base.Functor (LiveCoding.LiveProgram.Except.LiveProgramExcept m)
+ LiveCoding.LiveProgram.Except: instance GHC.Base.Monad m => GHC.Base.Monad (LiveCoding.LiveProgram.Except.LiveProgramExcept m)
+ LiveCoding.LiveProgram.Except: newtype LiveProgramExcept m e
+ LiveCoding.LiveProgram.Except: runLiveProgramExcept :: Monad m => LiveProgramExcept m e -> LiveProgram (ExceptT e m)
+ LiveCoding.LiveProgram.Except: safe :: Monad m => LiveProgram m -> LiveProgramExcept m Void
+ LiveCoding.LiveProgram.Except: safely :: Monad m => LiveProgramExcept m Void -> LiveProgram m
+ LiveCoding.LiveProgram.Except: try :: (Data e, Finite e, Functor m) => LiveProgram (ExceptT e m) -> LiveProgramExcept m e
+ LiveCoding.RuntimeIO.Launch: foreground :: Monad m => LiveProgram m -> m ()
+ LiveCoding.RuntimeIO.Launch: instance (Data.Data.Data e, LiveCoding.Exceptions.Finite.Finite e, LiveCoding.RuntimeIO.Launch.Launchable m) => LiveCoding.RuntimeIO.Launch.Launchable (Control.Monad.Trans.Except.ExceptT e m)
+ LiveCoding.RuntimeIO.Launch: instance (Data.Typeable.Internal.Typeable m, LiveCoding.RuntimeIO.Launch.Launchable m) => LiveCoding.RuntimeIO.Launch.Launchable (Control.Monad.Trans.State.Strict.StateT (LiveCoding.Handle.HandlingState m) m)
+ LiveCoding.RuntimeIO.Launch: liveMain :: Launchable m => LiveProgram m -> IO ()
- LiveCoding: (<<<) :: Category cat => cat b c -> cat a b -> cat a c
+ LiveCoding: (<<<) :: forall k cat (b :: k) (c :: k) (a :: k). Category cat => cat b c -> cat a b -> cat a c
- LiveCoding: (>>>) :: Category cat => cat a b -> cat b c -> cat a c
+ LiveCoding: (>>>) :: forall k cat (a :: k) (b :: k) (c :: k). Category cat => cat a b -> cat b c -> cat a c
- LiveCoding: ArrowMonad :: a () b -> ArrowMonad b
+ LiveCoding: ArrowMonad :: a () b -> ArrowMonad (a :: Type -> Type -> Type) b
- LiveCoding: ExceptT :: m (Either e a) -> ExceptT e a
+ LiveCoding: ExceptT :: m (Either e a) -> ExceptT e (m :: Type -> Type) a
- LiveCoding: Kleisli :: (a -> m b) -> Kleisli a b
+ LiveCoding: Kleisli :: (a -> m b) -> Kleisli (m :: Type -> Type) a b
- LiveCoding: LaunchedProgram :: MVar (LiveProgram IO) -> ThreadId -> LaunchedProgram
+ LiveCoding: LaunchedProgram :: MVar (LiveProgram IO) -> ThreadId -> LaunchedProgram (m :: * -> *)
- LiveCoding: Proxy :: Proxy
+ LiveCoding: Proxy :: Proxy (t :: k)
- LiveCoding: [HRefl] :: forall k1 k2 (a :: k1) (b :: k2). () => a :~~: a
+ LiveCoding: [HRefl] :: forall k1 (a :: k1). a :~~: a
- LiveCoding: [Refl] :: forall k (a :: k) (b :: k). () => a :~: a
+ LiveCoding: [Refl] :: forall k (a :: k). a :~: a
- LiveCoding: [programVar] :: LaunchedProgram -> MVar (LiveProgram IO)
+ LiveCoding: [programVar] :: LaunchedProgram (m :: * -> *) -> MVar (LiveProgram IO)
- LiveCoding: [runKleisli] :: Kleisli a b -> a -> m b
+ LiveCoding: [runKleisli] :: Kleisli (m :: Type -> Type) a b -> a -> m b
- LiveCoding: [threadId] :: LaunchedProgram -> ThreadId
+ LiveCoding: [threadId] :: LaunchedProgram (m :: * -> *) -> ThreadId
- LiveCoding: catchE :: Monad m => ExceptT e m a -> (e -> ExceptT e' m a) -> ExceptT e' m a
+ LiveCoding: catchE :: forall (m :: Type -> Type) e a e'. Monad m => ExceptT e m a -> (e -> ExceptT e' m a) -> ExceptT e' m a
- LiveCoding: eqT :: (Typeable a, Typeable b) => Maybe (a :~: b)
+ LiveCoding: eqT :: forall k (a :: k) (b :: k). (Typeable a, Typeable b) => Maybe (a :~: b)
- LiveCoding: except :: () => Either e a -> Except e a
+ LiveCoding: except :: forall (m :: Type -> Type) e a. Monad m => Either e a -> ExceptT e m a
- LiveCoding: gcast :: (Typeable a, Typeable b) => c a -> Maybe (c b)
+ LiveCoding: gcast :: forall k (a :: k) (b :: k) c. (Typeable a, Typeable b) => c a -> Maybe (c b)
- LiveCoding: gcast1 :: (Typeable t, Typeable t') => c (t a) -> Maybe (c (t' a))
+ LiveCoding: gcast1 :: forall k1 k2 c (t :: k2 -> k1) (t' :: k2 -> k1) (a :: k2). (Typeable t, Typeable t') => c (t a) -> Maybe (c (t' a))
- LiveCoding: gcast2 :: (Typeable t, Typeable t') => c (t a b) -> Maybe (c (t' a b))
+ LiveCoding: gcast2 :: forall k1 k2 k3 c (t :: k2 -> k3 -> k1) (t' :: k2 -> k3 -> k1) (a :: k2) (b :: k3). (Typeable t, Typeable t') => c (t a b) -> Maybe (c (t' a b))
- LiveCoding: gmapQr :: Data a => (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> a -> r
+ LiveCoding: gmapQr :: forall r r'. Data a => (r' -> r -> r) -> r -> (forall d. Data d => d -> r') -> a -> r
- LiveCoding: infixr 3 &&&
+ LiveCoding: infixr 3 ***
- LiveCoding: liftCallCC :: () => CallCC m (Either e a) (Either e b) -> CallCC (ExceptT e m) a b
+ LiveCoding: liftCallCC :: CallCC m (Either e a) (Either e b) -> CallCC (ExceptT e m) a b
- LiveCoding: liveCell :: Functor m => Cell m () () -> LiveProgram m
+ LiveCoding: liveCell :: Monad m => Cell m () () -> LiveProgram m
- LiveCoding: mapExcept :: () => (Either e a -> Either e' b) -> Except e a -> Except e' b
+ LiveCoding: mapExcept :: (Either e a -> Either e' b) -> Except e a -> Except e' b
- LiveCoding: mapExceptT :: () => (m (Either e a) -> n (Either e' b)) -> ExceptT e m a -> ExceptT e' n b
+ LiveCoding: mapExceptT :: (m (Either e a) -> n (Either e' b)) -> ExceptT e m a -> ExceptT e' n b
- LiveCoding: runExcept :: () => Except e a -> Either e a
+ LiveCoding: runExcept :: Except e a -> Either e a
- LiveCoding: runExceptT :: () => ExceptT e m a -> m (Either e a)
+ LiveCoding: runExceptT :: ExceptT e m a -> m (Either e a)
- LiveCoding: throwE :: Monad m => e -> ExceptT e m a
+ LiveCoding: throwE :: forall (m :: Type -> Type) e a. Monad m => e -> ExceptT e m a
- LiveCoding: typeRep :: Typeable a => proxy a -> TypeRep
+ LiveCoding: typeRep :: forall k proxy (a :: k). Typeable a => proxy a -> TypeRep
- LiveCoding: withExcept :: () => (e -> e') -> Except e a -> Except e' a
+ LiveCoding: withExcept :: (e -> e') -> Except e a -> Except e' a
- LiveCoding: withExceptT :: Functor m => (e -> e') -> ExceptT e m a -> ExceptT e' m a
+ LiveCoding: withExceptT :: forall (m :: Type -> Type) e e' a. Functor m => (e -> e') -> ExceptT e m a -> ExceptT e' m a
- LiveCoding.Cell: liveCell :: Functor m => Cell m () () -> LiveProgram m
+ LiveCoding.Cell: liveCell :: Monad m => Cell m () () -> LiveProgram m
- LiveCoding.GHCi: livelaunch :: Monad m => p -> m String
+ LiveCoding.GHCi: livelaunch :: Monad m => p -> m [Char]
- LiveCoding.RuntimeIO.Launch: LaunchedProgram :: MVar (LiveProgram IO) -> ThreadId -> LaunchedProgram
+ LiveCoding.RuntimeIO.Launch: LaunchedProgram :: MVar (LiveProgram IO) -> ThreadId -> LaunchedProgram (m :: * -> *)
- LiveCoding.RuntimeIO.Launch: [programVar] :: LaunchedProgram -> MVar (LiveProgram IO)
+ LiveCoding.RuntimeIO.Launch: [programVar] :: LaunchedProgram (m :: * -> *) -> MVar (LiveProgram IO)
- LiveCoding.RuntimeIO.Launch: [threadId] :: LaunchedProgram -> ThreadId
+ LiveCoding.RuntimeIO.Launch: [threadId] :: LaunchedProgram (m :: * -> *) -> ThreadId

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Revision history for essence-of-live-coding +## 0.2.5++* Refactored GHCi support+* Add `liveMain`+* Add exception monad for live programs+* Improved some haddocks+ ## 0.2.4  * Extended testing utilities
essence-of-live-coding.cabal view
@@ -1,5 +1,5 @@ name:                essence-of-live-coding-version:             0.2.4+version:             0.2.5 synopsis: General purpose live coding framework description:   essence-of-live-coding is a general purpose and type safe live coding framework.@@ -30,7 +30,7 @@ source-repository this   type:     git   location: git@github.com:turion/essence-of-live-coding.git-  tag:      v0.2.3+  tag:      v0.2.5   library@@ -57,6 +57,7 @@     , LiveCoding.Handle     , LiveCoding.Handle.Examples     , LiveCoding.LiveProgram+    , LiveCoding.LiveProgram.Except     , LiveCoding.LiveProgram.HotCodeSwap     , LiveCoding.LiveProgram.Monad.Trans     , LiveCoding.Migrate
src/LiveCoding.hs view
@@ -32,4 +32,4 @@ import LiveCoding.Migrate.Debugger as X import LiveCoding.Migrate.Migration as X import LiveCoding.RuntimeIO as X hiding (update)-import LiveCoding.RuntimeIO.Launch as X+import LiveCoding.RuntimeIO.Launch as X hiding (foreground)
src/LiveCoding/Bind.lhs view
@@ -52,13 +52,10 @@  \begin{code} sineWait-  :: Double-  -> CellExcept IO () String Void+  :: Double -> CellExcept IO () String Void sineWait t = do-  try  $   arr (const "Waiting...")-       >>> wait 2-  safe $   sine t-       >>> arr asciiArt+  try $ arr (const "Waiting...") >>> wait 2+  safe $ sine t >>> arr asciiArt \end{code} This \mintinline{haskell}{do}-block can be read intuitively. Initially, the first cell is executed,@@ -70,7 +67,7 @@ \begin{code} printSineWait :: LiveProgram IO printSineWait = liveCell-  $   safely (sineWait 10)+  $   safely (sineWait 8)   >>> printEverySecond \end{code} \verbatiminput{../demos/DemoSineWait.txt}
src/LiveCoding/Cell.lhs view
@@ -73,7 +73,14 @@ Let us call them cells, the building blocks of everything live: \begin{comment} \begin{code}--- | The basic building block of a live program.+{- | The basic building block of a live program.++You can build cells directly, by using constructors,+or through the 'Functor', 'Applicative', or 'Arrow' type classes.++The 'Cell' constructor is the main way build a cell,+but for efficiency purposes there is an additional constructor.+-} \end{code} \end{comment} \begin{code}@@ -84,13 +91,17 @@ \end{code} \begin{comment} \begin{code}+  -- ^ A cell consists of an internal state,+  --   and an effectful state transition function.   | ArrM { runArrM :: a -> m b }-  -- ^ Added to improve performance and keep state types simpler+  -- ^ Effectively a cell with trivial state.+  --   Added to improve performance and keep state types simpler. \end{code} \end{comment} \begin{comment} \begin{code} -- | Converts every 'Cell' to the 'Cell' constructor.+--   Semantically, it is the identity function. toCell :: Functor m => Cell m a b -> Cell m a b toCell cell@Cell {} = cell toCell ArrM { .. } = Cell@@ -105,7 +116,12 @@ not only the updated cell, but also an output datum \mintinline{haskell}{b}: +\begin{comment} \begin{code}+-- | Execute a cell for one step.+\end{code}+\end{comment}+\begin{code} step   :: Monad m   => Cell m a b@@ -122,6 +138,8 @@  \begin{comment} \begin{code}+-- | Execute a cell for several steps.+--   The number of steps is determined by the length of the list of inputs. steps   :: Monad m   => Cell m a b@@ -136,7 +154,12 @@ \end{comment}  As a simple example, consider the following \mintinline{haskell}{Cell} which adds all input and returns the delayed sum each step:+\begin{comment} \begin{code}+-- | Add all inputs and return the delayed sum.+\end{code}+\end{comment}+\begin{code} sumC :: (Monad m, Num a, Data a) => Cell m a a sumC = Cell { .. }   where@@ -145,14 +168,22 @@ \end{code}  We recover live programs as the special case of trivial input and output:+\begin{comment} \begin{code}+-- | Convert a cell with no inputs and outputs to a live program.+--   Semantically, this is an isomorphism.+\end{code}+\end{comment}+\begin{code} liveCell-  :: Functor     m+  :: Monad m   => Cell        m () ()   -> LiveProgram m liveCell Cell { .. } = LiveProgram   { liveState = cellState-  , liveStep  = fmap snd . flip cellStep ()+  , liveStep  = \state -> do+      (_, state') <- cellStep state ()+      return state'   } \end{code} \begin{comment}@@ -165,6 +196,7 @@ \end{comment} \begin{comment} \begin{code}+-- | The inverse to 'liveCell'. toLiveCell   :: Functor     m   => LiveProgram m@@ -176,13 +208,15 @@ \end{code} \end{comment} -\subsection{FRP for automata-based programming}-Effectful Mealy machines, here cells,-offer a wide variety of applications in FRP.+\subsection{FRP for Automata-Based Programming}+Our cells are known in the literature as ``Effectful Mealy Machines'', ``transducers'' and ``resumptions''+\cite{MILNER1975157}, \cite{pirog2014coinductive}, \cite[Section 7]{hasuo_jacobs_2011}, \cite[Section 5.4]{AbramskyHaghverdiScott}.+They are known for their relevance to stream functions \cite{CaspiPouzet},+suggesting that they offer a wide variety of applications in FRP. The essential parts of the API, which is heavily inspired by the FRP library \texttt{dunai} \cite{Dunai},-is shown here.+are shown here. %\mintinline{haskell}{Cell}s can be composed in three directions: %Sequentially and parallely in the data flow sense, %and sequentially in the control flow sense.@@ -202,12 +236,11 @@ \end{comment}  \paragraph{Composition}-By being an instance of the type class \mintinline{haskell}{Category}-for any monad \mintinline{haskell}{m},+By being an instance of the type class \mintinline{haskell}{Category},+% for any monad \mintinline{haskell}{m}, cells implement sequential composition: \begin{spec}-(>>>)-  :: Monad m+(>>>) :: Monad m   => Cell  m a b   -> Cell  m   b c   -> Cell  m a   c@@ -258,8 +291,13 @@ The step function executes the steps of both cells after each other. They only touch their individual state variable, the state stays encapsulated.+The custom data\-type is isomorphic to an ordinary Haskell tuple \mintinline{haskell}{(state1, state2)}.+Yet it is beneficial to introduce it,+since it allows us to extend the migration function easily such that it correctly handles the common case where we live change a cell \mintinline[style=bw]{haskell}{cellMiddle} to a composition,+such as \mintinline[style=bw]{haskell}{cellLeft >>> cellMiddle},+or to \mintinline[style=bw]{haskell}{cellMiddle >>> cellRight}. -\fxwarning{Reuse Sensor, SF and Actuator later?}+\paragraph{The Sensor-SF-Actuator-Pattern} Composing \mintinline{haskell}{Cell}s sequentially allows us to form live programs out of \emph{sensors}, pure signal functions and \emph{actuators}: \begin{code} type Sensor   a   = Cell   IO         () a@@ -267,30 +305,24 @@ type Actuator   b = Cell   IO              b () \end{code} \begin{code}-buildLiveProg-  :: Sensor   a-  -> SF       a b-  -> Actuator   b+buildProg :: Sensor a -> SF a b -> Actuator b   -> LiveProgram IO-buildLiveProg sensor sf actuator = liveCell+buildProg sensor sf actuator = liveCell   $ sensor >>> sf >>> actuator \end{code}-This (optional) division of the reactive program into three such parts is inspired by Yampa \cite{Yampa}.+This (optional) division of the reactive program into three such parts is inspired by Yampa \cite{Yampa},+and was formulated in this way in \cite[Section 7.1.2]{Dunai}. We conveniently build a whole live program from smaller components. It is never necessary to specify a big state type manually, it will be composed from basic building blocks like \mintinline{haskell}{Composition}. -The migration function is easily extended such that it correctly handles the common cases where we extend a cell \mintinline{haskell}{cellMiddle} to the composition \mintinline{haskell}{cellLeft >>> cellMiddle},-or to \mintinline{haskell}{cellMiddle >>> cellRight}.- \paragraph{Arrowized FRP}-\mintinline{haskell}{Cell}s can be made an instance of the \mintinline{haskell}{Arrow} type class,+\mintinline{haskell}{Cell}s are an instance of the \mintinline{haskell}{Arrow} type class, which allows us to lift pure functions to \mintinline{haskell}{Cell}s: \begin{spec} arr-  :: Monad m-  ->         (a -> b)-  -> Cell  m  a    b+  :: Monad m => (a -> b)+  -> Cell  m     a    b \end{spec} \fxwarning{Would be nice to have the space to explain *** as well!} Together with the \mintinline{haskell}{ArrowChoice} and \mintinline{haskell}{ArrowLoop} classes@@ -307,7 +339,7 @@ For simplicity and explicitness, assume that we will execute all \mintinline{haskell}{Cell}s at a certain fixed step rate, say, twenty five steps per second.-Then an Euler integration cell can be defined:+Then Euler integration can be defined: \begin{code} stepRate :: Num a => a stepRate = 25@@ -328,14 +360,13 @@  \fxwarning{I cut a more detailed discussion about ArrowChoice and ArrowLoop here. Put in the appendix?} -\paragraph{Monads and their morphisms}+\paragraph{Monads and Their Morphisms} Beyond standard arrows, a \mintinline{haskell}{Cell} can encode effects in a monad, so it is not surprising that Kleisli arrows can be lifted: \begin{spec} arrM-  :: Monad m-  ->         (a -> m b)-  -> Cell  m  a      b+  :: Monad m => (a -> m b)+  -> Cell  m     a      b \end{spec} \begin{comment} Mere monadic actions become a special case thereof:@@ -349,18 +380,28 @@  In case our \mintinline{haskell}{Cell} is in another monad than \mintinline{haskell}{IO}, one can define a function that transports a cell along a monad morphism:+\begin{comment} \begin{code}+-- | Hoist a 'Cell' along a monad morphism.+\end{code}+\end{comment}+\begin{code} hoistCell   :: (forall x . m1 x   ->      m2 x)   ->        Cell m1 a b -> Cell m2 a b \end{code} For example, we may eliminate a \mintinline{haskell}{ReaderT r} context by supplying the environment through the \mintinline{haskell}{runReaderT} monad morphism, or lift into a monad transformer:+\begin{comment} \begin{code}+-- | Lift a 'Cell' into a monad transformer.+\end{code}+\end{comment}+\begin{code} liftCell-  :: (Monad m, MonadTrans t)-  => Cell         m  a b-  -> Cell      (t m) a b+  :: (Monad  m, MonadTrans t)+  => Cell    m  a b+  -> Cell (t m) a b liftCell = hoistCell lift \end{code} As described in \cite[Section 4]{Dunai},@@ -448,8 +489,14 @@ \end{code} \end{comment} -\subsection{A sine generator}-Making use of the \mintinline{haskell}{Arrows} syntax extension,+\subsection{A Sine Generator}+Making use of the \mintinline{haskell}{Arrows} syntax extension\footnote{%+Arrow notation -- or \mintinline{haskell}{proc .. do} notation --+is similar to monadic \mintinline{haskell}{do} notation,+except that not only is there a dedicated binder \mintinline{haskell}{<-} for output values,+but also an application operator \mintinline{haskell}{-<} for \emph{input} values.+The notation is desugared into the arrow operators,+such as \mintinline{haskell}{arr} and the composition \mintinline{haskell}{>>>}.}, we can implement a harmonic oscillator that will produce a sine wave with amplitude 10 and given period length: \fxwarning{Comment on rec and ArrowFix} \fxerror{I want to add a delay for numerical stability}@@ -487,7 +534,7 @@     else returnA       -< () \end{code} Our first live program-written in FRP is ready:+written in FRP is assembled using the pattern of sensor, signal function and actuator: \begin{code} printSine :: Double -> LiveProgram IO printSine t = liveCell@@ -521,7 +568,7 @@ if we use it in a video application, the widget will smoothly change its oscillating velocity without a jolt. -\section{Control flow}+\section{Control Flow} \label{sec:control flow} \fxerror{Show only stuff where I can show most of the implementation. Reimplement, in a separate file, the API for the newtype, show its code and explain it.} Although we now have the tools to build big signal pathways from single cells,@@ -531,15 +578,14 @@ We are lacking permanent \emph{control flow}.  The primeval arrowized FRP framework Yampa \cite{Yampa} caters for this requirement by means of switching from a signal function to another if an event occurs.+Such mechanisms are well studied, e.g. in \cite{WinogradHudak2014settable}. \fxwarning{Possibly I've mentioned both earlier} Dunai \cite[Section 5.3]{Dunai}, taking the monadic aspect seriously, \fxwarning{Dunai, Yampa -> \texttt{Dunai} etc.?} rediscovers switching as effect handling in the \mintinline{haskell}{Either} monad.-\begin{comment}-We shall see that,-although the state of a \mintinline{haskell}{Cell} is strongly restricted by the \mintinline{haskell}{Data} type class,-we can get very close to this powerful approach to control flow.-\end{comment}+Although the state of a \mintinline{haskell}{Cell} is strongly restricted by the \mintinline{haskell}{Data} type class,+we can reimplement this powerful approach to control flow with few alterations,+and make typical control flow patterns such as exception handling and looping amenable to live coding without further effort.  \begin{comment} \begin{code}
src/LiveCoding/Cell/Util.hs view
@@ -47,7 +47,7 @@  -- | Initialise with a value 'a'. --   If the input is 'Nothing', @keep a@ will output the stored indefinitely.---   A new value can be stored by inputting 'Maybe a'.+--   A new value can be stored by inputting @'Just' a@. keep :: (Data a, Monad m) => a -> Cell m (Maybe a) a keep a = feedback a $ proc (ma, aOld) -> do   let aNew = fromMaybe aOld ma@@ -80,7 +80,7 @@  -- | Print the current UTC time, prepended with the first 8 characters of the given message. printTime :: MonadIO m => String -> m ()-printTime msg = liftIO $ putStrLn =<< ((take 8 msg) ++) . show <$> getCurrentTime+printTime msg = liftIO $ putStrLn . (take 8 msg ++) . show =<< getCurrentTime  -- | Like 'printTime', but as a cell. printTimeC :: MonadIO m => String -> Cell m () ()
src/LiveCoding/Debugger.lhs view
@@ -28,7 +28,7 @@ \end{code} \end{comment} -\subsection{Debugging the live state}+\subsection{Debugging the Live State} Having the complete state of the program in one place allows us to inspect and debug it in a central place. We might want to interact with the user, display aspects of the state
src/LiveCoding/Exceptions.lhs view
@@ -28,7 +28,7 @@ \end{code} \end{comment} -\paragraph{Throwing exceptions}+\paragraph{Throwing Exceptions} No new concepts beyond the function \mintinline{haskell}{throwE :: Monad m => e -> ExceptT e m a} from the package \texttt{transformers} \cite{jones1995functional, transformers} are needed: \begin{code}@@ -38,7 +38,7 @@ throwC = arrM throwE \end{code} The above function simply throws the incoming exception.-To do this only if a certain condition is satisfied,+To do this only if a condition is satisfied, \mintinline{haskell}{if}-constructs can be used. For example, this cell forwards its input for a given number of seconds, and then throws an exception:@@ -67,7 +67,7 @@ \end{code} \end{comment} -\paragraph{Handling exceptions}+\paragraph{Handling Exceptions} In usual Haskell, the \mintinline{haskell}{ExceptT} monad transformer is handled by running it: \begin{spec} runExceptT :: ExceptT e m b -> m (Either e b)
src/LiveCoding/Forever.lhs view
@@ -25,7 +25,7 @@ \end{code} \end{comment} -\subsection{Exceptions forever}+\subsection{Exceptions Forever}  \fxwarning{Opportunity to call this an SF here (and elsewhere)} What if we want to change between the oscillator and a waiting period indefinitely?@@ -35,11 +35,8 @@   :: MonadFix   m   => CellExcept m () String () sinesWaitAndTry = do-  try $   arr (const "Waiting...")-      >>> wait 1-  try $   sine 5-      >>> arr asciiArt-      >>> wait 5+  try $ arr (const "Waiting...") >>> wait 1+  try $ sine 5 >>> arr asciiArt  >>> wait 5 \end{code} \fxwarning{wait is an unintuitive name. Sounds blocking. "forwardFor"?} The one temptation we have to resist is to recurse in the \mintinline{haskell}{CellExcept} context to prove the absence of exceptions:@@ -54,12 +51,12 @@ It typechecks, but it does \emph{not} execute correctly. \fxerror{Why does it hang? Does it really hang?} As the initial state is built up,-the definition of \mintinline{haskell}{sinesForever'} inquires about the initial state of all cells in the \mintinline{haskell}{do}-expression,-but last one is again \mintinline{haskell}{foo},+this definition inquires about the initial state of all cells in the \mintinline{haskell}{do}-expression,+but the last one is again \mintinline{haskell}{sinesForever'}, and thus already initialising such a cell hangs in an infinite loop. Using the standard function \mintinline{haskell}{forever :: Applicative f => f a -> f ()} has the same deficiency, \fxerror{Have we tested that?}-as it is defined in essentially the same way.+as it is defined in the same way.  The resolution is an explicit loop operator, and faith in the library user to remember to employ it.@@ -71,9 +68,8 @@   -> Cell                        m   a b \end{code} The loop function receives as arguments an initial exception,-and a cell that is to be executed forever\footnote{%+and a cell that is to be executed forever. Of course, the monad \mintinline{haskell}{m} may again contain exceptions that can be used to break from this loop.-}. \begin{comment} \begin{code} foreverE e (Cell state step) = Cell { .. }
src/LiveCoding/GHCi.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-} {- | Support functions to call common live coding functionalities like launching and reloading from a @ghci@ or @cabal repl@ session. @@ -10,44 +14,94 @@  -- base import Control.Concurrent+import Control.Exception (SomeException, try)+import Control.Monad (void, (>=>))+import Data.Data+import Data.Function ((&)) +-- transformers+import Control.Monad.Trans.State.Strict+ -- foreign-store import Foreign.Store  -- essence-of-live-coding import LiveCoding.LiveProgram-import LiveCoding.RuntimeIO import LiveCoding.RuntimeIO.Launch -livelaunch _ = return $ unlines-  [ "launchedProgram <- launch liveProgram"-  , "save launchedProgram"-  ]+proxyFromLiveProgram :: LiveProgram m -> Proxy m+proxyFromLiveProgram _ = Proxy -livestop _ = return "stop launchedProgram"+-- * Retrieving launched programs from the foreign store --- | Load a 'LiveProgram' of a given type from the store.---   The value of the given 'LiveProgram' is not used,---   it only serves as a proxy for m.-load :: Launchable m => LiveProgram m -> IO (LaunchedProgram m)-load _ = readStore $ Store 0+-- | Try to retrieve a 'LiveProgram' of a given type from the 'Store',+--   handling all 'IO' exceptions.+--   Returns 'Right Nothing' if the store didn't exist.+possiblyLaunchedProgram+  :: Launchable m+  => Proxy m+  -> IO (Either SomeException (Maybe (LaunchedProgram m)))+possiblyLaunchedProgram _ = do+  storeMaybe <- lookupStore 0+  try $ traverse readStore storeMaybe +++-- | Try to load a 'LiveProgram' of a given type from the 'Store'.+--   If the store doesn't contain a program, it is (re)started.+sync :: Launchable m => LiveProgram m -> IO ()+sync program = do+  launchedProgramPossibly <- possiblyLaunchedProgram $ proxyFromLiveProgram program+  case launchedProgramPossibly of+    -- Looking up the store failed in some way, restart+    Left (e :: SomeException) -> putStrLn "exc" >> launchAndSave program+    -- The store was empty, restart+    Right Nothing -> putStrLn "empty" >> launchAndSave program+    -- A program is running, update it+    Right (Just launchedProgram) -> putStrLn "update" >> update launchedProgram program++-- | Launch a 'LiveProgram' and save it in the 'Store'.+launchAndSave :: Launchable m => LiveProgram m -> IO ()+launchAndSave = launch >=> save+ -- | Save a 'LiveProgram' to the store. save :: Launchable m => LaunchedProgram m -> IO () save = writeStore $ Store 0 +-- | Try to retrieve a 'LaunchedProgram' from the 'Store',+--   and if successful, stop it.+stopStored+  :: Launchable m+  => Proxy m+  -> IO ()+stopStored proxy = void $ (fmap $ fmap $ fmap stop) $ possiblyLaunchedProgram proxy++-- * GHCi commands++-- ** Debugging -- TODO Could also parametrise this and all other commands by the 'liveProgram' +-- | Initialise a launched program in the store,+--   but don't start it. liveinit _ = return $ unlines   [ "programVar <- newMVar liveProgram"   , "threadId <- myThreadId"   , "save LaunchedProgram { .. }"   ] +-- | Run one program step, assuming you have a launched program in a variable @launchedProgram@.+livestep _ = return "stepLaunchedProgram launchedProgram"++-- ** Running++-- | Launch or restart a program and save its reference in the store.+livelaunch _ = return "sync liveProgram"++-- | Reload the code and do hot code swap and migration. livereload _ = return $ unlines   [ ":reload"-  , "launchedProgram <- load liveProgram"-  , "update launchedProgram liveProgram"+  , "sync liveProgram"   ] -livestep _ = return "stepLaunchedProgram launchedProgram"+-- | Stop the program.+livestop _ = return "stopStored $ proxyFromLiveProgram liveProgram"
src/LiveCoding/LiveProgram.lhs view
@@ -19,7 +19,11 @@ \fxerror{Split up even more into the individual modules and integrate more with the code}  Our model of a live program will consist of a state and an effectful state transition function.-A preliminary version is shown in Figure \ref{fig:LiveProgramPreliminary}.+A preliminary version is shown in Figure \ref{fig:LiveProgramPreliminary}\footnote{%+The notation \mintinline{haskell}{$} may be unfamiliar.+It can be read as "apply brackets until the end of the following expression".+For example, \mintinline{haskell}{f $ g $ h a b c} is essentially the same as \mintinline{haskell}{f (g (h a b c))}.+}. \input{../essence-of-live-coding/src/LiveCoding/Preliminary/LiveProgram/LiveProgramPreliminary.lhs} The program is initialised at a certain state, and from there its behaviour is defined by repeatedly applying the function \mintinline{haskell}{liveStep} to advance the state and produce effects.@@ -56,7 +60,9 @@ where we have such a typechecker. It immediately points out the unsafety of the migration: There is no guarantee that the new transition function will typecheck with the old state!-In fact, in many situations, the state type needs to be extended or modified.+In fact, in many situations, the state type needs to be modified,+and there is no function of type \mintinline{haskell}{LiveProgram m s -> LiveProgram m s'}+already because there is no function of type \mintinline{haskell}{s -> s'}.  This kind of problem is not unknown. In the world of databases,@@ -70,13 +76,15 @@ If a column has to be deleted, its data will not be recoverable. In turn, if a column is created, one has to supply a sensible default value (often \verb|NULL| will suffice). -\subsection{Migrating the state}+\subsection{Migrating the State}  We can straightforwardly adopt this solution by thinking of the program state as a small database table with a single row. Its schema is the type \mintinline{haskell}{s}. Given a \emph{type migration} function, we can perform hot code swap, as shown in Figure \ref{fig:hot code swap}.+We need to supply the old live program, the new live program,+and a suitable migration function. \input{../essence-of-live-coding/src/LiveCoding/Preliminary/LiveProgram/HotCodeSwap.lhs} This may be an acceptable solution to perform a planned, well-prepared intervention, but it does spoil the fun in a musical live coding performance if the programmer has to write a migration function after every single edit.@@ -111,7 +119,7 @@ But in the following, we are going to see how to tweak the live program definition by only twenty characters, and arrive at an effective migration function. -\subsection{Type-driven migrations}+\subsection{Type-Driven Migrations} In many cases, knowing the old state and the new initial state is sufficient to derive the new, migrated state safely. As an example, imagine the internal state of a simple webserver that counts the number of visitors to a page. \fxwarning{Later show how migrate behaves on these examples}@@ -204,17 +212,23 @@ that our webserver has become wildly popular, and \mintinline{haskell}{nVisitors} is close to \mintinline{haskell}{maxInt}. We need to migrate this value to an arbitrary precision \mintinline{haskell}{Integer}.-It is easy to extend \mintinline{haskell}{migrate} by a special case provided by the user:+It is easy to extend \mintinline{haskell}{migrate} by a special case provided by the user,+shown in Figure \ref{fig:user migration}.+\begin{figure} \begin{spec} userMigrate   :: (Data a, Data b, Typeable c, Typeable d)   => (c -> d)   -> a -> b -> a-+\end{spec}+\begin{spec} intToInteger :: Int -> Integer intToInteger = toInteger \end{spec}-In our example, we would use \mintinline{haskell}{userMigrate intToInteger} to migrate the state.+\caption{User migration}+\label{fig:user migration}+\end{figure}+Here, we would use \mintinline{haskell}{userMigrate intToInteger} to migrate the state. \fxwarning{Show example. Extend runtime.}  To use the automatic migration function,@@ -270,7 +284,7 @@  \input{../essence-of-live-coding/src/LiveCoding/RuntimeIO.lhs} -\subsection{Live coding a webserver}+\subsection{Live Coding a Webserver}  \fxwarning{Consider redoing this as a GHCi session where we call the server from within Haskell, e.g. with the curl or a HTTP package} 
+ src/LiveCoding/LiveProgram/Except.hs view
@@ -0,0 +1,109 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{- | Live programs in the @'ExceptT' e m@ monad can stop execution by throwing an exception @e@.++Handling these exceptions is done by realising that live programs in fact form a monad in the exception type.+The interface is analogous to 'CellExcept'.+-}+module LiveCoding.LiveProgram.Except where++-- base+import Control.Monad (liftM, ap)+import Data.Data++-- transformers+import Control.Monad.Trans.Except+import Control.Monad.Trans.Reader++-- essence-of-live-coding+import LiveCoding.Cell (hoistCell, toLiveCell, liveCell)+import LiveCoding.CellExcept (CellExcept, runCellExcept)+import LiveCoding.Exceptions.Finite (Finite)+import LiveCoding.Forever+import LiveCoding.LiveProgram+import qualified LiveCoding.CellExcept as CellExcept+import Data.Void (Void)++{- | A live program that can throw an exception.++* @m@: The monad in which the live program operates.+* @e@: The type of exceptions the live program can eventually throw.++'LiveProgramExcept' is a monad in the exception type.+This means that it is possible to chain several live programs,+where later programs can handle the exceptions thrown by the earlier ones.+'return' plays the role of directly throwing an exception.+'(>>=)' lets a handler decide which program to handle the exception with.++The interface is the basically the same as 'CellExcept',+and it is in fact a newtype around it.+-}+newtype LiveProgramExcept m e = LiveProgramExcept+  { unLiveProgramExcept :: CellExcept m () () e }+  deriving (Functor, Applicative, Monad)++-- | Execute a 'LiveProgramExcept', throwing its exceptions in the 'ExceptT' monad.+runLiveProgramExcept+  :: Monad m+  => LiveProgramExcept m e+  -> LiveProgram (ExceptT e m)+runLiveProgramExcept LiveProgramExcept { .. } = liveCell $ runCellExcept unLiveProgramExcept++{- | Lift a 'LiveProgram' into the 'LiveProgramExcept' monad.++Similar to 'LiveProgram.CellExcept.try'.+This will execute the live program until it throws an exception.+-}+try+  :: (Data e, Finite e, Functor m)+  => LiveProgram (ExceptT e m)+  -> LiveProgramExcept m e+try = LiveProgramExcept . CellExcept.try . toLiveCell++{- | Safely convert to 'LiveProgram's.++If the type of possible exceptions is empty,+no exceptions can be thrown,+and thus we can safely assume that it is a 'LiveProgram' in @m@.+-}+safely+  :: Monad m+  => LiveProgramExcept m Void+  -> LiveProgram m+safely = liveCell . CellExcept.safely . unLiveProgramExcept++{- | Run a 'LiveProgram' as a 'LiveProgramExcept'.++This is always safe in the sense that it has no exceptions.+-}+safe+  :: Monad m+  => LiveProgram m+  -> LiveProgramExcept m Void+safe = LiveProgramExcept . CellExcept.safe . toLiveCell++{- | Run a 'LiveProgramExcept' in a loop.++In the additional 'ReaderT e' context,+you can read the last thrown exception.+(For the first iteration, 'e' is set to the first argument to 'foreverELiveProgram'.)++This way, you can create an infinite loop,+with the exception as the loop variable.+-}+foreverELiveProgram+  :: (Data e, Monad m)+  => e -- ^ The loop initialisation+  -> LiveProgramExcept (ReaderT e m) e -- ^ The live program to execute indefinitely+  -> LiveProgram                  m+foreverELiveProgram e LiveProgramExcept { .. } = liveCell $ foreverE e $ hoistCell commute $ runCellExcept unLiveProgramExcept+  where+    commute :: ExceptT e (ReaderT r m) a -> ReaderT r (ExceptT e m) a+    commute action = ReaderT $ ExceptT . runReaderT (runExceptT action)++-- | Run a 'LiveProgramExcept' in a loop, discarding the exception.+foreverCLiveProgram+  :: (Data e, Monad m)+  => LiveProgramExcept m e+  -> LiveProgram       m+foreverCLiveProgram LiveProgramExcept { .. } = liveCell $ foreverC $ runCellExcept unLiveProgramExcept
src/LiveCoding/Preliminary/CellExcept.lhs view
@@ -36,7 +36,7 @@ try = CellExcept id \end{code} And we can leave it safely once we have proven that there are no exceptions left to throw,-i.e. the exception type is empty:+i.e. the exception type is empty (represented in Haskell by \mintinline{haskell}{Void}): \fxerror{I'm using runCellExcept which wasn't explained yet} \begin{code} safely
src/LiveCoding/Preliminary/CellExcept/Newtype.lhs view
@@ -15,7 +15,7 @@ \end{code} \end{comment} -\subsection{Control flow context}+\subsection{Control Flow Context} \label{sec:control flow context} %\paragraph{Wrapping exceptions} Inspired by \cite[Section 2, "Control Flow through Exceptions"]{Rhine},@@ -37,17 +37,21 @@ try = CellExcept \end{code} And we can leave it safely once we have proven that there are no exceptions left to throw,-i.e. the exception type is empty:+i.e. the exception type is empty (represented in Haskell by \mintinline{haskell}{Void}): \begin{code} safely   :: Monad      m   => CellExcept m a b Void   -> Cell       m a b+\end{code}+\begin{comment}+\begin{code} safely = hoistCell discardVoid . runCellExcept   where     discardVoid       = fmap (either absurd id) . runExceptT \end{code}+\end{comment} One way to prove the absence of further exceptions is, of course, to run an exception-free cell: \begin{code}@@ -55,10 +59,14 @@   :: Monad      m   => Cell       m a b   -> CellExcept m a b Void+\end{code}+\begin{comment}+\begin{code} safe cell = CellExcept $ liftCell cell \end{code}+\end{comment} -\paragraph{The return of the monad}+\paragraph{The Return of the Monad} Our new hope is to give \mintinline{haskell}{Functor}, \mintinline{haskell}{Applicative} and \mintinline{haskell}{Monad} instances to \mintinline{haskell}{CellExcept}. We will explore now how this allows for rich control flow. @@ -89,11 +97,14 @@ it can also be defined from the \emph{live bind} operator \mintinline{haskell}{>>>=} introduced previously. As a technical tour-de-force, even a \mintinline{haskell}{Monad} instance for \mintinline{haskell}{CellExcept} can be derived with some modifications.-This is shown at length in the appendix.+This is shown at length in an appendix\footnote{%+Available online at \href{https://www.manuelbaerenz.de/essence-of-live-coding/EssenceOfLiveCodingAppendix.pdf}{https://www.manuelbaerenz.de/essence-of-live-coding/EssenceOfLiveCodingAppendix.pdf}.+}.  But how can \mintinline{haskell}{Applicative} and \mintinline{haskell}{Monad} be put to use? The foreground value of \mintinline{haskell}{CellExcept} is the thrown exception. With \mintinline{haskell}{pure}, such values are created, and \mintinline{haskell}{Functor} allows us to perform computations with them.-The classes \mintinline{haskell}{Applicative} and \mintinline{haskell}{Monad} allow us to \emph{chain} the execution of exception throwing cells:+With \mintinline{haskell}{Applicative} and \mintinline{haskell}{Monad},+we \emph{chain} the execution of exception throwing cells: \fxwarning{Comment on how Monad is even stronger than Applicative?}
src/LiveCoding/RuntimeIO.lhs view
@@ -17,14 +17,14 @@ import LiveCoding.LiveProgram.HotCodeSwap import LiveCoding.Debugger import LiveCoding.Migrate-import LiveCoding.RuntimeIO.Launch+import LiveCoding.RuntimeIO.Launch hiding (foreground) \end{code} \end{comment} -\section{The runtime}+\section{The Runtime} \label{sec:runtime} -\subsection{Hands on interaction}+\subsection{Hands on Interaction} Enough declaration. Let us get semantic and run some live programs! In the preliminary version,@@ -35,9 +35,9 @@ We could of course run the program in the foreground thread: \begin{code} foreground :: Monad m => LiveProgram m -> m ()-foreground liveProgram-  =   stepProgram liveProgram-  >>= foreground+foreground liveProgram = do+  liveProgram' <- stepProgram liveProgram+  foreground liveProgram' \end{code} But this would leave no possibility to exchange the program with a new one. %But this would then become the main loop,@@ -96,6 +96,7 @@ Using \texttt{ghcid} (``GHCi as a daemon'' \cite{ghcid}), the launching and reloading operations can be automatically triggered upon starting \texttt{ghcid} and editing the code, allowing for a smooth live coding experience without any manual intervention.+\fxerror{Update according to the latest sync function}  In the next subsection, a full example is shown.
src/LiveCoding/RuntimeIO/Launch.hs view
@@ -8,17 +8,20 @@ -- base import Control.Concurrent import Control.Monad+import Data.Data  -- transformers import Control.Monad.Trans.State.Strict+import Control.Monad.Trans.Except  -- essence-of-live-coding import LiveCoding.Debugger import LiveCoding.Handle import LiveCoding.LiveProgram+import LiveCoding.LiveProgram.Except import LiveCoding.LiveProgram.HotCodeSwap import LiveCoding.Cell.Monad.Trans-import Data.Data (Typeable)+import LiveCoding.Exceptions.Finite (Finite)  {- | Monads in which live programs can be launched in 'IO', for example when you have special effects that have to be handled on every reload.@@ -32,9 +35,37 @@ instance Launchable IO where   runIO = id -instance Launchable (StateT (HandlingState IO) IO) where-  runIO = runHandlingState+instance (Typeable m, Launchable m) => Launchable (StateT (HandlingState m) m) where+  runIO = runIO . runHandlingState +-- | Upon an exception, the program is restarted.+--   To handle or log the exception, see "LiveCoding.LiveProgram.Except".+instance (Data e, Finite e, Launchable m) => Launchable (ExceptT e m) where+  runIO liveProgram = runIO $ foreverCLiveProgram $ try liveProgram++{- | The standard top level @main@ for a live program.++Typically, you will define a top level 'LiveProgram' in some monad like @'HandlingStateT' 'IO'@,+and then add these two lines of boiler plate:++@+main :: IO ()+main = liveMain liveProgram+@+-}+liveMain+  :: Launchable m+  => LiveProgram m+  -> IO ()+liveMain = foreground . runIO++-- | Launch a 'LiveProgram' in the foreground thread (blocking).+foreground :: Monad m => LiveProgram m -> m ()+foreground liveProgram+  =   stepProgram liveProgram+  >>= foreground++-- | A launched 'LiveProgram' and the thread in which it is running. data LaunchedProgram (m :: * -> *) = LaunchedProgram   { programVar :: MVar (LiveProgram IO)   , threadId   :: ThreadId@@ -61,9 +92,8 @@   => LaunchedProgram m   -> LiveProgram     m   -> IO ()-update LaunchedProgram { .. } newProg = do-  oldProg <- takeMVar programVar-  putMVar programVar $ hotCodeSwap (runIO newProg) oldProg+update LaunchedProgram { .. } newProg = modifyMVarMasked_ programVar+  $ return . hotCodeSwap (runIO newProg)  {- | Stops a thread where a 'LiveProgram' is being executed. @@ -105,4 +135,4 @@   :: (Monad m, Launchable m)   => LaunchedProgram m   -> IO ()-stepLaunchedProgram LaunchedProgram { .. } = modifyMVar_ programVar stepProgram+stepLaunchedProgram LaunchedProgram { .. } = modifyMVarMasked_ programVar stepProgram