Description
With the current design of the logging primitives, it is possible to easily run into inconsistent behavior. For example:
main = keep $ restore $ do
r <- choose [1..5 :: Int]
logged $ liftIO $ print ("A",r)
suspend ()
liftIO $ print ("B",r)
Here, the programmer forgot to add logged
to the choose
primitive. This results in an incorrect output. Another example, if we use suspend
without logging, the program always suspends:
main = keep $ do
r <- choose [1..5 :: Int]
liftIO $ print ("A",r)
suspend ()
liftIO $ print ("B",r)
Instead we should either throw an error or ignore the suspend since we are not logging.
A potential solution to these problems is to design the API such that we use the logged
primitive one level above the suspend
. If the parent is logged then logging is inherited by all the child computations and we can suspend a child. Since we know the logged/not logged state we can change the behavior of suspend
based on that. Also, the logged primitive itself can do the job of restore as well. The code will look like:
main = keep $ logged $ do
r <- choose [1..5 :: Int]
liftIO $ print ("A",r)
suspend ()
liftIO $ print ("B",r)
I have not thought much about it, so I am not sure if this solution has any inherent problems.