BlockArgument and $

A few weeks ago someone came with a way of abusing the BlockArgument extension by pointing that $ could be replaced in same case with do. For example

example1 = map (+1) do [1..5] ++ [10..20]

is equivalest to

example2  = map (+1) $ [1..5] ++ [10..20]

Moreover using do has the advantage of over $ to be layout aware, it doesn’t span until the end of the expression and can be nested

exampleZip =
    zip
        do
           map (*10) [1..5]
        do
           map do (+1)
               do [1..5] ++ [10..20]

(compare with the parenthesis or $ version).
I initially was shocked; do notation is not made for that, it is abusing the current syntax and shouldn’t be used. However, the more I am thinking of it the more I like it (even though I haven’t used it in production … yet).
The obvious case for $ is, we are use to it and do is for Monad so it is confusing. Moreover this is fluke, the fact that do something when something is not a Monad works is a GHC bug and might be fixed, youn shouldn’t rely on it.

The counter argument are , we are only used to $ because we have seen it and most us might actually have taken some time to get use to it. Moreover, $ on the same expression is frown upon and the dot police often requires you to change a $ b $ c d to a . b $ c d.
The main reason I don’t like $ ... $ is because I am never sure of how it is parsed and its precedenc rule. This is solved by using do.

Anyway, the main argument for do (beside the obvious layout benefit) is it is a keyword, not a mere function.

Being a keyword means that it is part of the language, does not generate anything at runtime.
‘$’ on the other hand is just a function, which needs to be erased and has special treatment from GHC so that it works as we expect.

So maybe we should embrace this new style (or have a proposal for a new extension replacing layout with parenthesis alltogether making the do trick obsolete).

6 Likes

It’s about readability, ultimately.

As for $, $ is idiomatic, but it’s alienating to outsiders. On the other hand, while do helps with Haskell’s operator-ridden syntax, it’s similarly confusing to outsiders who have no idea what weird nesting Matryoshka you built abusing do notation’s layout-based approach.

There’s also the {} option you might be interested in investigating, although it’s not as powerful as you’d like (I can’t do foo = {} definitions).

3 Likes

What I mean is this use of do can become idiomatic too if use it that way.

6 Likes

But it can also lead to fractured idioms wherein half the community is on $, the other half is on do, and there’s style wars between the $ and do communities.

Really, try it out; for a library I was working on, I was using builder pattern via WriterT (i.e, a datatype that can be expressed via do notation) until acquaintances pointed out they hated its use in Reflex and Blaze. I thought about other considerations, then decided to remove it.

1 Like

the fact that do something when something is not a Monad works is a GHC bug and might be fixed, you shouldn’t rely on it.

Where did you hear the claim that it is a bug? AFAICT this is a natural consequence of treating do as syntactic sugar for a bunch of >>= and >>, which you only need with at least two statements. (See Haskell report.)

do x <- M ; Ms   =   M >>= \x -> do Ms
do M ; Ms        =   M >> do Ms
do M             =   M

The last rule is the base case for this translation. Restricting do to two or more statements, or imposing a Monad constraint seem like ad hoc and unnecessary restrictions.

The reliance on layout can be detrimental. Imagine

f do x
  do y

gets “refactored” to

f do x do y
7 Likes

We’ve been using do as a replacement for $ at work for a while and I love it.

I specially like using do for separating the three arguments in bracket

8 Likes

I use it a lot, it’s good for providing multiple arguments to a function without parenthesizing.

5 Likes

No, it’s much more than that:

…there’s one way to avoid those annoying “style wars” : don’t use Haskell.

1 Like

I’m greatly surprised by this, even though it seems obvious in hindsight. I don’t know whether it should be considered good style or not. I’m going to try it out.

2 Likes

Maybe we could get another keyword that replicates this behaviour while keeping do to syntactic sugar?

1 Like

Or no keyword at all and make function application itself layout sensitive. E.g. you’d be able to write:

exampleZip =
    zip map (*10) [1..5]
        map (+1)
            [1..5] ++ [10..20]
9 Likes

That’s what I’ve been wanting for years. Another solution is a new keyword introducing a layout sensitive block.

2 Likes

Nobody said it was a bug, I meant it might be a bug, not sure do M was foreseen when the do keyword was introduce. If not you dont even need the in` keyword as

withIn = 
    let x = 3
        y = 10
    in x + y

can be rewritten

withoutIn = do
    let x = 3
        y = 19
    x + y

I like the idea, but maybe introducing a new keyword for something which already exists is OTT.
However using $ as the new keyword might be a good idea.

Is it? Doesn’t Identity admit a Monad instance?

do you mean that do (5 :: Int) as Identity Int type ?

Either way, the Haskell2010 report does not specify that do only works for things that have Monad instances, it merely specifies how it’s desugared. In other words, I wouldn’t call it a bug, GHC’s behavior follows the specification.

1 Like

Indeed however it is still accidental.If you activate ApplicativeDo will it still work ? According to the documentation

Note: the final statement must match one of these patterns exactly:

  • return E
  • return $ E
  • pure E
  • pure $ E


If the final statement is not of one of these forms, GHC falls back to standard do desugaring, and the expression will require a Monad constraint.

Should do 5 (final statement not being pure or return) require a Monad constraint ?

3 Likes

Tbh, it’s not so confusing. You can see do one-liners as “do this expression”, which might be easier to explain to newbies.

Still, I think we might be getting ahead of ourselves. Maybe a year or two of “the most hateful usage of do” before we judge whether any lexical extensions are warranted?

Also:

For people who insist on brackets:

foo = do {myexp;}

:wink:

It works with ApplicariveDo. We have both extensions enabled in all modules

1 Like