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âŚ