\begin{comment}
\begin{code}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE StandaloneDeriving #-}
module LiveCoding.Debugger where
import Control.Concurrent
import Control.Monad (void)
import Data.Data
import Data.IORef
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State
import Data.Generics.Text
import LiveCoding.LiveProgram
import LiveCoding.Cell
\end{code}
\end{comment}
\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
and possibly even change it in place.
In short, a debugger is a program that can read and modify,
as an additional effect,
the state of an arbitrary live program:
\begin{code}
newtype Debugger m = Debugger
{ getDebugger :: forall s .
Data s => LiveProgram (StateT s m)
}
\end{code}
\begin{comment}
\begin{code}
instance Monad m => Semigroup (Debugger m) where
debugger1 <> debugger2 = Debugger $ getDebugger debugger1 <> getDebugger debugger2
instance Monad m => Monoid (Debugger m) where
mempty = Debugger mempty
getC :: Monad m => Cell (StateT s m) a s
getC = constM get
putC :: Monad m => Cell (StateT s m) s ()
putC = arrM put
\end{code}
\end{comment}
A simple debugger prints the unmodified state to the console:
\begin{code}
gshowDebugger :: Debugger IO
gshowDebugger = Debugger
$ liveCell $ arrM $ const $ do
state <- get
lift $ putStrLn $ gshow state
\end{code}
Thanks to the \mintinline{haskell}{Data} typeclass,
the state does not need to be an instance of \mintinline{haskell}{Show} for this to work:
\texttt{syb} offers a generic \mintinline{haskell}{gshow} function.
A more sophisticated debugger could connect to a GUI and display the state there,
even offering the user to pause the execution and edit the state live.
\fxwarning{Should I explain countDebugger? What for?}
\fxerror{Following a comment on cat theory. Add to appendix?}
\begin{comment}
Debuggers are endomorphisms in the Kleisli category of \mintinline{haskell}{IO},
and thus \mintinline{haskell}{Monoid}s:
A pair of them can be chained by executing them sequentially,
and the trivial debugger purely \mintinline{haskell}{return}s the state unchanged.
\end{comment}
We can bake a debugger into a live program:
\begin{code}
withDebugger
:: Monad m
=> LiveProgram m
-> Debugger m
-> LiveProgram m
\end{code}
\begin{comment}
\begin{code}
withDebugger = (liveCell .) . withDebuggerC . toLiveCell
withDebuggerC
:: Monad m
=> Cell m a b
-> Debugger m
-> Cell m a b
withDebuggerC (Cell state step) (Debugger (LiveProgram dbgState dbgStep)) = Cell { .. }
where
cellState = Debugging { .. }
cellStep Debugging { .. } a = do
(b, state') <- step state a
states <- runStateT (dbgStep dbgState) state'
return (b, uncurry (flip Debugging) states)
\end{code}
\end{comment}
Again, let us understand the function through its state type:
\begin{code}
data Debugging dbgState state = Debugging
{ state :: state
, dbgState :: dbgState
} deriving (Data, Eq, Show)
\end{code}
On every step, the debugger becomes active after the cell steps,
and is fed the current \mintinline{haskell}{state} of the main program.
Depending on \mintinline{haskell}{dbgState},
it may execute some side effects or mutate the \mintinline{haskell}{state},
or do nothing at all\footnote{%
This option is important for performance: E.g. for an audio application,
a side effect on every sample can slow down unbearably.}.
Live programs with debuggers are started just as usual.
\begin{comment}
\fxwarning{Automatise this and the next output}
Inspecting the state of the example \mintinline{haskell}{printSineWait} from Section \ref{sec:control flow context} is daunting, though:
\begin{verbatim}
Waiting...
(Composition ((,) (Composition ((,) (())
(Composition ((,) (()) (Composition ((,)
(Composition ((,) (()) (Composition ((,)
[...]
\end{verbatim}
\fxerror{I still have the tuples here!}
The arrow syntax desugaring introduces a lot of irrelevant overhead such as compositions with the trivial state type \mintinline{haskell}{()},
hiding the parts of the state we are actually interested in.
Luckily, it is a simple, albeit lengthy exercise in generic programming to prune all irrelevant parts of the state,
resulting in a tidy output%\footnote{%
%Line breaks were added to fit the columns.}
like:
\end{comment}
Let us inspect the state of the example \mintinline{haskell}{printSineWait} from Section \ref{sec:control flow context}.
It is a simple, albeit lengthy exercise in generic programming to prune all irrelevant parts of the state when printing it,
resulting in a tidy output like:
\fxwarning{Automatise this}
\begin{verbatim}
Waiting...
NotThrown: (1.0e-3)
>>> +(0.0) >>> (0.0)+ >>> (1)
NotThrown: (2.0e-3)
>>> +(0.0) >>> (0.0)+ >>> (2)
[...]
Waiting...
NotThrown: (2.0009999999998906)
>>> +(0.0) >>> (0.0)+ >>> (2001)
Exception:
>>> +(3.9478417604357436e-3) >>> (0.0)+
>>> (2002)
[...]
\end{verbatim}
\begin{comment}
Exception:
>>> +(7.895683520871487e-3) >>>
(3.947841760435744e-6)+
>>> (2003)
\end{comment}
The cell is initialised in a state where the exception hasn't been thrown yet,
and the \mintinline{haskell}{localTime} is \mintinline{haskell}{1.0e-3} seconds.
The next line corresponds to the initial state (position and velocity) of the sine generator which will be activated after the exception has been thrown,
followed by the internal counter of \mintinline{haskell}{printEverySecond}.
In the next step, local time and counter have progressed.
Two thousand steps later, the exception is finally thrown,
and the sine wave starts.
\begin{comment}
\begin{code}
newtype CountObserver = CountObserver { observe :: IO Integer }
countDebugger :: IO (Debugger IO, CountObserver)
countDebugger = do
countRef <- newIORef 0
observeVar <- newEmptyMVar
let debugger = Debugger $ liveCell $ arrM $ const $ lift $ do
n <- readIORef countRef
putMVar observeVar n
yield
void $ takeMVar observeVar
writeIORef countRef $ n + 1
observer = CountObserver $ yield >> readMVar observeVar
return (debugger, observer)
await :: CountObserver -> Integer -> IO ()
await CountObserver { .. } nMax = go
where
go = do
n <- observe
if n > nMax then return () else go
\end{code}
\end{comment}