Maintaining haskell programs

As for BlockArguments in particular:

{-# LANGUAGE BlockArguments #-}
module BlockArgsBedlam where

f :: a -> b -> Char
f _ _ = '?'

mt :: IO ()
mt = putChar '~'

mu :: Maybe Bool
mu = Just True

test1 = f do mt

test2 = f do mu

test3 = f do mt do mu

test4 = f do mt $ do mu

…which GHCi rejects:

#  ghci BlockArgsBedlam.hs
GHCi, version 9.4.4: https://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling BlockArgsBedlam  ( BlockArgsBedlam.hs, interpreted )

BlockArgsBedlam.hs:17:14: error:
    • Couldn't match expected type: Maybe Bool -> a0
                  with actual type: IO ()
    • The function ‘mt’ is applied to one value argument,
        but its type ‘IO ()’ has none
      In a stmt of a 'do' block: mt do mu
      In the first argument of ‘f’, namely ‘do mt do mu’
   |
17 | test3 = f do mt do mu
   |              ^^^^^^^^

BlockArgsBedlam.hs:19:14: error:
    • Couldn't match expected type: Maybe Bool -> a1
                  with actual type: IO ()
    • In the first argument of ‘($)’, namely ‘mt’
      In a stmt of a 'do' block: mt $ do mu
      In the first argument of ‘f’, namely ‘do mt $ do mu’
   |
19 | test4 = f do mt $ do mu
   |              ^^
Failed, no modules loaded.
ghci>

So BlockArguments is just another syntactic “sugar rush” which doesn’t scale up for general use:

…less of it is usually better.

1 Like

TBH, the real drawback of language extensions is incessant arguing among engineers / developers over language extensions (which to use, whether they’re a good idea, and how they actually work).

But I agree with @Underlap; we need better documentation, specifically, tutorials, for the language extensions.

Haskell has come a long way from where every feature was essentially just documented by an academic paper, but Haskell documentation is not yet at the level where you can throw a codemonkey at it and get something that works.

Some Haskellers might feel this to be a feature, I feel this to be a bug.

2 Likes

Thank you for relating your experience – this is almost worth a separate thread.

The whole document set got restructured at 9.0, which means a lot of material ended up in unfamiliar (to me) places. (Probably the new structure is more ‘logical’; it’s the changeover that’s leaving people disoriented.) And it’s been tweaked a bit since.

Specifically the docos on FlexibleContexts hasn’t got much terser AFAICT. Are you perhaps confusing with FlexibleInstances? Which is indeed chunky, and rightfully so. I’da thought any project that uses one would use both.

Yep. And it’s not included in GHC2021/nor likely to be included in GHC20XX, so it’s not worth an ‘advanced Haskell’ tutorial that puts all these extensions together into some coherent explanatory whole.

The reason is there’s another approach in the same design space as TypeFamilies. FunctionalDependencies are older, but still widely used, and widely preferred for some purposes. (Although I suspect this is more to do with personal taste and familiarity.)

To achieve any sort of modern Haskell, your project will need one or the other; quite possibly both. (If you try to stick to one in your project, quite possibly some imports will use the other.)

I’d say Haskell is at that ‘awkward age’ between being a wild, experimental teenager and settling down with responsibilities. There isn’t (yet) the sort of corporate monoculture you get in more commercial languages.

5 Likes

Haha I would be shocked if Haskell ever had anything resembling a programming-style monoculture in my lifetime. That’s kind of the fun part though :grin:

@AntC2 wrote:

Thank you for relating your experience – this is almost worth a separate thread.

I’ve started a separate thread, How to learn language extensions, for those who’ve “tuned out” this thread.

Specifically the docos on FlexibleContexts hasn’t got much terser AFAICT. Are you perhaps confusing with FlexibleInstances? Which is indeed chunky, and rightfully so. I’da thought any project that uses one would use both.

My link to the latest docs for FlexibleContexts was indeed correct and nothing to do with FlexibleInstances. Shall we continue in the other thread?

2 Likes

At my last job, we did too - it was great! I really don’t like the style that Ormolu uses, but I like configuring formatters and debating said configuration even less :slight_smile:

4 Likes

I used to really dislike it but now that I’m used to it it’s no longer a question of like or dislike, it just is, and having formatting concerns off my mind is a breath of fresh air!

3 Likes

This was also my experience.

2 Likes

I don’t get the extension/formatting/codestyle/dependencies/strings/etc “choices” problem.

  • You are a sole developer. You use whatever the hell you want.
  • You are a project owner/maintainer. You use whatever you deem necessary to keep your project in a good shape.
  • You are a project contributor. You use whatever they already use in there. Don’t like something – submit a proposal, but the last word is on them.
6 Likes

While yes BlockArguments does cause some parsing behavior to change, to me it’s a massive net-win overall. I’m afraid to say that your example is just contrived, no one would want to write that code. In fact I have no idea what it’s meant to parse as without BlockArguments. OTOH, the code I do write with BlockArguments to me remains unambiguous, just less $y. I would personally take the now unparseable code and have BlockArguments as the default, but that ship has sailed and the breakage would be too high. Agda got it right here.

7 Likes

For me Fourmolu hit the sweet spot. I also pushed back for the longest time on formatting, and as I think @george.fst said to be at ZuriHac - once you relax and let that go, you stop caring. At least, I did. I find Ormolu a bit too extreme, but

indentation: 2
comma-style: leading
import-export-style: diff-friendly
indent-wheres: true
record-brace-space: false
respectful: true
haddock-style: multi-line
newlines-between-decls: 2
fixities: []
single-constraint-parens: auto

is perfectly acceptable to me.

Honestly having a code formatter has made me more productive, because I can now just throw code at my editor, ignore the context switch about formatting (maybe hit Enter a few times because I know I’ll want something formatted over multiple lines), and then move on with real work.

8 Likes

I don’t understand what that means. What is “take the now unparseable code”?

I meant I would accept the parse error. Essentially I would like BlockArguments on by default, and I would be OK if the example @atravers gave produced a parse error. I’m not actually suggesting this though, because the breakage from changing that default would be too high.

2 Likes

You are a middle manager responsible for a slice of a very large codebase, a codebase that few understand in its entirety, you…

1 Like

While true, I’ve had good luck for doing syntactic linting using tree-grepper. Your linting rule here would be:

tree-grepper -q haskell '((exp_infix (operator) @_op (exp_do)) (#eq? @_op "$")) @e' .
2 Likes

Oh I see, you mean you would be willing to accept that @atraver’s example is unparsable in exchange for having BlockArguments be the default way of parsing. Personally I’ve always been scared of BlockArguments but enough sensible people are in favour of it that I should probably give it another look.

Well, I don’t actually remember saying that, but it certainly sounds plausible! Although despite thinking I didn’t care about formatting, I did actually spend much of late 2020 bringing Fourmolu to life because I just didn’t find Ormolu’s style palatable.

Nowadays, I really don’t ever think about formatting. But it probably helps that the Fourmolu defaults are essentially my personal preferences.

1 Like

…you say “contrived” ; I say “inspired” :-D


Really? I would have thought it would be the logical consequence of allowing a do-block to be the only (i.e. last) argument of a function call - why stop at “just one”/“this special case”?


Surely Agda doesn’t allow things like:

go do ... {- :: M1 A1 -}
   do ... {- :: M2 A2 -}
    ⋮

…if you forget to write a do, what was supposed to be two consecutive blocks will be treated as one!

Oh this is an easy one!

…you give your team budget to do the work needed to wrangle the codebase. Tests, refactoring into smaller components, etc. This isn’t even a technical or Haskell problem when you’re in that failure mode.

Also, some manager somewhere probably messed up to get you in that situation. A large codebase doesn’t have to be tangled together. At scale, you are rewarded for code units aligning with team units (monorepo or not - packages exist). This isn’t novel - Bezos figured that out two decades ago.

Sometimes you gotta go slow in order to go fast.

3 Likes

BlockArguments are not a trivial step forward in the design of Haskell. It is a big step forward. It is not only about dropping the $. Way more than that. It lets you write like so:

{-# language BlockArguments, UnicodeSyntax #-}

example = fmap
  do \x → x^2 + 1
  do [1, 2, 3]

Whenever you need to give an industrial size function a bunch of industrial size arguments, this makes life much easier. Grouping symbols that go in matching twosomes create big cognitive load because humans are bad at finding matching parentheses. You folks tried writing any JavaScript? The amount of parentheses, braces and curlies you must match soon gets overwhelming, it becomes like a mini game. Trading curlies for indentation was a good idea, and trading parentheses for indentation is the natural extension of it that makes Haskell even lighter, even easier to read.

There is also a way to trade braces for indentation — since lists are monoids and monoids are monads, we can again write do.

{-# language BlockArguments #-}

import Control.Monad.Writer

say = tell . pure

example = execWriter @[_] do
  say 'I'
  say '💛'
  say 'U'

This is handy when you have big nested list-like stuff, like say when building aeson Value values by hand, or when writing test suites with tasty. No more pain of replacing comma with bracket and bracket with comma when you want to swap the first line with another line. Compare with the bracket and comma notation:

example' =
  [ 'I'
  , '💛'
  , 'U'
  ]

How many key strokes would it take you to swap the I and the U?

This is also good for the diff size, which makes maintenance easier.

4 Likes