But also be careful where you use it. Haskell is non-strict whereas Lean4 and Idris are strict, so not all of their “syntactic sweeteners” can be trivially reimplemented in Haskell.
Nowadays if I want this I use monadic-bang. It’s nice that it doesn’t have to be an extension, but I still think maybe it should be, because it reveals a lot of minor issues with plugins (summarised under “Caveats”).
The caveats I was referring to are all to do with poor source locations and error messages from GHC and lack of support in GHCi. I’m saying it would be better to fix this so that plugins don’t suffer from these issues, but possibly easier to get better UX from an extension.
I’m fine with the scoping semantics. To me this doesn’t feel much different from any other “hoisted” inline structure—lambdas, string literals, string interpolation, comprehensions, and so on.
It’s true that (<- m1) && <- m2 has the same issue as m1 <&&> m2 where (<&&>) = liftA2 (&&). If you want sequencing of effects, you need something like m1 <&&> m2 = m1 >>= \case { True -> m2; False -> pure False }. My feeling is that this doesn’t worsen the learning pitfalls that Haskell already has about execution vs. evaluation, and I personally don’t mind just adding another level of do if it’s really necessary, like when !m1 do when !m2 ….
…if these problems afflict most other plugins: certainly! But if it’s mostly a problem for “semantics-bending” plugins such as monadic-bang, it may not be so easily solved by expanding GHC(i)'s plugin infrastructure.
Hmm - that begs the question: how well doesmonad-bang work with do-do notation? As for those “learning pitfalls”…they’re usually associated with monadic types having visible (outside the program) effects - other monadic types like ST s a, Either c a, Maybe a and [a] seem to be less confusing with regards to their execution versus their evaluation.
But as the examples in the OP clearly show, Lean’s monadic !-notation also works with IO a, the original monadic type having visible effects. On that point, and also from the second example:
…because Lean is strict, therefore both arguments to (&&) would be evaluated first.
But what stops Lean from transforming that example into:
def f (x y : Nat) : IO Unit := do
if (<- isGreaterThan0 y) && (<- isGreaterThan0 x) then
IO.println s!"{x} and {y} are greater than 0"
else
pure ()
…is Lean more than just strict - does it also preserve the ordering of (what appear to be) expressions? By default Haskell doesn’t, and for good reason:
Again, it might just be simpler to use Lean rather than try to duplicate its monadic !-notation in Haskell…