tweag / monad-bayes

A library for probabilistic programming in Haskell.
MIT License
401 stars 62 forks source link

Is there a reason for no MonadFail instance? #306

Open luke-clifton opened 11 months ago

luke-clifton commented 11 months ago

Sometimes it would be neat to be able to use a fallible pattern match in do-notation, similar to what we get with lists. This is enabled by having a MonadFail instance.

I was wondering if there was some reason why there is no MonadFail instance available that I can not see?

reubenharry commented 11 months ago

No reason that comes to mind, although do you have a specific example use case in mind? I've certainly used an ExceptT transformer to a similar effect.

luke-clifton commented 10 months ago

It just seems kinda neater in some scenarios. Especially if the sampling is abstracted and we can't "fix" it at the source.

enumerator $ do
   a <- uniformD [Just 1, Just 2, Nothing]
   condition (isJust a)
   pure a
enumerator $ do
   Just a <- uniformD [Just 1, Just 2, Nothing]
   pure a
turion commented 10 months ago

[lengthy comment following] Your two examples even have different types, the first being MonadMeasure m => m (Maybe Int), the second being MonadMeasure m => m Int. Which is more attractive because we know that only the Just path has nonzero measure, and the Nothing path cannot enter any observable in the end.

To achieve that effect, one would like to write:

enumerator $ do
  aMaybe <- uniformD [Just 1, Just 2, Nothing]
  case aMaybe of
    Just a -> pure a
    Nothing -> do
      score 0
      error "Impossible"

But unfortunately, there is no law in MonadScore that prevents anything after 0 score to be unreachable, i.e. a law like score 0 >> ma = score 0. Such a law should hold approximately in any reasonable probability monad, I believe, but I'm not sure it is mentioned anywhere in the original article. I don't know the literature on probability monads well enough to say whether this is a common law. Anyways it is not enforced in the implementations.

I read your proposal as adding such a possibility to the interface via MonadFail. The semantic of fail is supposed to mean "don't continue traversing this branch", like mzero.

luke-clifton commented 10 months ago

Yes, the types being different was a transcription error.

And yes, I had noticed that condition was not quite the same as guard in the Enumerator type, in exactly the way you describe. The computation doesn't "stop" with condition. That was going to be a follow up question. (In fact, when I want to use Enumerator I often use guard instead of condition for that reason and it seems to work as expected).

So your conclusion is correct, I'd actually like to have a Alternative (and possibly MonadPlus) instance to go along with the MonadFail instance. These (bar MonadFail) are there for Enumerator which I do find useful, but then can't use when I want to use a different probability monad.