bwo / monads

yet another monad lib for clojure
93 stars 8 forks source link

First expression in `mdo` is eagerly evaluated #5

Open rymndhng opened 8 years ago

rymndhng commented 8 years ago

I'm seeing that the first expression inside an mdo is always evaluated, which may execute side-effects before the monad is run.

For example:

(mdo 
  failure <- (throw (RuntimeException. "AHA!"))
  _ <- (right :something)      
  :success)

This throws the runtime exception before I try to execute it with monads.core/run-monad

bwo commented 8 years ago

Yeah, I've actually run into this but haven't fixed it (obviously). A kind of stupid workaround for now is to just insert a do-nothing return expression at the beginning. Thanks for the report though, will work on a fix.

bwo commented 8 years ago

I'll just drop some thoughts in here ...

Actually, on further thought, there is a real question about how to treat this. The above example translates thus:

(>>= (throw (RuntimeException. "AHA!"))
     (fn [failure] (>>= (right :something)
                        (fn [_] :success))))

And if you think of mdo as just sugar for a series of nested binds, then this is quite correct (and the reason for the problem becomes apparent, since >>= is a regular function). Basically, we always have eager evaluation, it's just that the evaluation of the later expressions doesn't take place until their post-macroexpansion enclosing functions are called. (Poor man's laziness, whereas Haskell has real laziness from the get-go so even the initial expression isn't evaluated until demanded.)

Of course it's possible to have mdo insert some extra fanciness (or just to insert the not-fancy workaround mentioned above, at the cost of traversing an extra bind at the beginning), but that make the relationship between mdo and >>= & friends less obvious. (>>= and >> etc. could also just be macros that delay their first argument, but that seems like it would involve a lot of overhead.)

It's still a PITA of course.

rymndhng commented 8 years ago

Agree with your concerns. Thinking about it as syntactic sugar makes sense to "not" be the libraries problem.

My background isn't in haskell, but my mental model for mdo is that whatever is inside the mdo is expected to be lazy so that I can pass the computation around and potentially evaluate it in different contexts, i.e try/catch and transaction.

Again, I'm not sure if there's a good solution for this -- though at minimal maybe this could be added to the README.