[Haskell-cafe] Help on syntactic sugar for combining lazy & strict monads?
Benjamin Redelings
benjamin.redelings at gmail.com
Tue Jul 20 18:37:01 UTC 2021
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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/haskell-cafe/attachments/20210720/6aa467cd/attachment.html>
More information about the Haskell-Cafe
mailing list