Help on syntactic sugar for combining lazy & strict monads?

Hi, I'm working on a probabilistic programming language with Haskell syntax [1]. I am trying to figure out how to intermingle lazy and strict monadic code without requiring ugly-looking markers on the lazy code. Does anybody have insights on this? 1. I'm taking a monadic approach that is similar to [2], but I'm using a lazy interpreter. This allows code such as the following, which would not terminate under a strict interpreter: run_lazy $ do xs <- sequence $ repeat $ normal 0 1 return $ take 10 xs Here "xs" is an infinite list of Normal(0,1) random variables, of which only 10 are returned. In a strict interpreter the line for "xs" never completes. But in a lazy interpreter, it works fine. 2. However, a lazy interpreter causes problems when trying to introduce *observation* statements (aka conditioning statements) into the monad [3]. For example, run_lazy $ do x <- normal 0 1 y <- normal x 1 z <- normal y 1 2.0 `observe_from` normal z 1 return y In the above code fragment, y will be forced because it is returned, and y will force x. However, the "observe_from" statement will never be forced, because it does not produce a result that is demanded. 3. My current approach is to use TWO monads -- one for random sampling (Sample a), and one for observations (Observe a). The random sampling monad can be lazy, because for random samples there is no need to force a sampling event if the result is never used. The observation monad is strict, because all the observations must be forced. So this WORKS fine. However... the code looks ugly :-( Help? 4a. One idea is to nest the lazy code within the strict monad, using some kind of tag "sample :: Sample a -> Observe a". Then we could write something in the (Observe a) monad like: run_strict $ do w <- sample $ sequence $ repeat $ normal 0 1 x <- sample $ normal 0 1 2.0 `observe_from` normal x 1 y <- sample $ normal x 1 z <- sample $ normal y 1 2.0 `observe_from` normal z 1 return y When the "run_strict" interpreter encounters a statement of the form (sample $ _ ), it switches to the "run_lazy" interpreter for that statement. In this case, the `observe_from` statement IS forced because it is in the strict (Observe a) monad. Maybe somewhat surprisingly w, x, y, and z are forced (ugh!) -- by the outer strict interpreter, not the inner lazy interpreter. However, the internal components of w are NOT forced, so the program is able to terminate. QUESTION: Is there some way of doing this without manually writing "sample" in front of all the sampling operations? QUESTION: is there a way of doing this where the "sample $ _" lines do NOT have their result forced? 4b. In order to write "sample" less, it is possible to factor the sampling code into a separate function (here called "prior"): prior :: Sample ([Double], Double, Double, Double) prior = do w <- sequence $ repeat $ normal 0 1 x <- normal 0 1 y <- normal x 1 z <- normal y 1 return (w,x,y,z) model :: Observe Double model = do (w,x,y,z) <- sample $ prior 2.0 `observe_from` normal x 1 2.0 `observe_from` normal z 1 return y This does mean that you have to write "sample" only once... but it (i) splits the function in half and (ii) forces you to explicitly pass (w,x,y,z) between the two functions. That obfuscates the code for no benefit. Interestingly, the logical conclusion of this movement of code from (Observe a) to (Sample a) is to move EVERYTHING to the Sample monad: prior :: Sample (Observe (), Double) prior = do w <- sequence $ repeat $ normal 0 1 x <- normal 0 1 let observations1 = [2.0 `observe_from` normal x 1] y <- normal x 1 z <- normal y 1 let observations2 = [2.0 `observe_from` normal z 1]++observations1 return (observations2,y) model :: Observe Double model = do (observations, y) <- sample $ prior sequence_ observations return y Now, we have moved all the observations into the (Sample a) monad, but using horrible syntax! Ugh :-( Also, now the function "model" is basically the same for all models -- the entire model has been moved into the prior. QUESTION: is there some way to write a monadic function like "prior" while accumulating things observations in a list? Thanks for any insights! -BenRI [1] http://bali-phy.org/models.php [2] Practical probabilistic programming with monads, https://dl.acm.org/doi/10.1145/2804302.2804317 [3] https://github.com/tweag/monad-bayes/issues/32
participants (1)
-
Benjamin Redelings