Why not BlockArguments?

I don’t use BlockArguments because I try to minimize the number of extensions I use, but what’s the argument of keeping BlockArguments out of GHC editions? I’m under the impression that it’s always on the verge of being added to the next GHC20xx, but something stops it. But then again, LambdaCase was in the same situation.

3 Likes

Apparently no strong opposition, but not much love either.

The relevant ticket similarly has little discussion about BlockArguments, no-one wrote against it, supporting voices were few.

Community was more receptive of the idea.

3 Likes

I personally love BlockArguments and use it all over the place, but it’s a very minor improvement, especially if ($) doesn’t feel a bit dirty to you. If your discipline is to use per-module language pragmas for every extension, it feels like a pretty large amount of boilerplate for such a minor improvement. Counting characters is not my favorite way to appraise syntax sugar, but since it’s a simple metric, consider how many uses of ($) I need to be able to eliminate in order to pay for the characters used by the language pragma.

{-# Language BlockArguments #-}

$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $

0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6

If this is my criteria, my module must have sixteen ($)s that I can eliminate before I would even consider it worth enabling, and then I will end up changing a lot of code.

A different approach is to always enable the extension by default and just consider it part of your standard module boilerplate, that way the barrier is paid for in advance, and you might as well just use block arguments instead of ($) immediately when you write the code, but that feels costly, too. I, personally, add it to the default-extensions in my .cabal file, but I understand that most people like to enable extensions more locally to aide comprehension.

All of this would be addressed by enabling the extension by default, but default extensions usually have to be pretty universally loved already, which is tough due to the boilerplate. It’s a chicken and egg problem that makes BlockArguments a bit difficult to get in via a voting process.

5 Likes

I love BlockArguments, it makes code significantly more readable. I hope it gets included in the next edition by default.

12 Likes

it’s a very minor improvement, especially if ($) doesn’t feel a bit dirty to you.

100% agree. I like it myself, but I recognize it’s a marginal improvement. If you already have the habit of putting $ where you need it, I can totally understand why you wouldn’t get over the activation energy to switch your style to BlockArguments.

That said, it seems like we could have a lower threshold for including “small” extensions like this as defaults. (As far as I know) BlockArguments does not break any existing code and there’s no real cost to enabling it. It’s only an extension for historical reasons; if that syntax had been valid from the beginning, nobody would bat an eye at it.

People regularly cite Haskell extensions as something that makes the language harder to learn. Language editions are the mechanism we currently have to help manage this. Rolling small, backwards-compatible extensions into editions would be a sort of “extension housekeeping”: ideally we’d be able to start treating these changes as just parts of the base language rather than extensions that newcomers have to explicitly track and understand.

I don’t know how effective language editions are in this regard but, since they’re our primary option right now, I’m in favor of using them more rather than less :slight_smile:

5 Likes

BlockArguments has a large con in that there’s currently no way for the compiler or external tool like hlint to tell you that $ is redundant. The result is that you can either use $ or not and this introduces inconsistency and potential pointless discussions during code review, so I never enable it.

5 Likes

I think discussing whether a $ is redundant in code review just shouldn’t happen lol. Not worth the time it takes to type it or read it :laughing:

4 Likes

I feel like this is already constantly the case in Haskell though, isn’t it?
E.g. you might write f (g (h x)) or f $ g $ h x or even f . g . h $ x (or anything in between).
All of these could be pointlessly argued about, depending on which style people prefer

2 Likes

Okay, next objection?

16 Likes

BlockArguments is not so much a language extension as a bugfix for the spec—it’s almost always the first “extension” I enable.

I would have argued strongly for its inclusion in GHC2021 or GHC2024, but frankly I never noticed any of these discussions were taking place.

7 Likes

So, I guess, when GHC2027 comes up for discussion, people will be reminded to go plug “There’s no good reason not to LambdaCase BlockArguments”?

2 Likes

I think you folks are missing the primary benefit (or down side, depending on who you ask) of BlockArguments here: Avoiding parentheses that are not possible to avoid with $ (and without contorting your code). For example:

findUser >>= mapM \case
  Nothing -> ...
  Just user -> ...

Notice how you can’t just insert a $ after mapM here. In general, you must understand that the syntactic role of a “block argument” simply isn’t comparable to $ having a low precedence. If you’re thinking in terms of precedence, then a block form has an asymmetric precedence. I.e. consider do as the simplest analogy to $. In many cases, you can just replace a $ with a do if you have BlockArguments: putStrLn do "Hello" ++ "World", but consider how these are not the same putStrLn . toUpper $ "Hello" ++ "World" vs. putStrLn . toUpper do "Hello" ++ "World". Coming back to my asymmetric precedence point: $ has the lowest precedence, so it binds the last, both to its left hand side (putStrLn . toUpper) and to its right hand side ("Hello" ++ "World"), but do acts as if it has very high precedence on the left hand side (so it binds to toUpper), but a very low precedence on the right hand side (So it doesn’t bind to “Hello”).

Another example is:

parseAppArgs :: Parser AppArgs
parseAppArgs = AppArgs
  <$> do some . complex $ 
           expression
  <*> do other . complex $
           expressions
  <*> ...

In this case, BlockArguments is even more magical than just “asymmetric precedence”.

9 Likes

I still remember @maxigit being crazy about this back in the day, is do bulletting now mainstream and considered an essential feature? I recall it was controversial when it was last discussed.

1 Like

I honestly don’t know what the community thinks, but I wouldn’t apologise for using it as in my parseAppArgs example above. But then again, I’d probably use ApplicativeDo + RecordWildCards for that anyway. I reckon that you can go overboard with this stuff, but I don’t feel bad about using syntactic conveniences for making the code clearer.

1 Like

Also, to note, this code actually works without block arguments because the operators are separating the do blocks.

But I can see where you’re coming from, given that I’ve become rather enamored of calling functions with vertical arguments; vertical code is less dense and has more space for inline comments.

2 Likes

Ha, yeah, good catch :slight_smile: I was too focused on blocks being more magical than just operator precedence, so didn’t even consider whether this was an example of BlockArguments.