GHC2024 – community input

I mentioned that normally you’d have a dangling do (i.e. putting a newline after the do), but used a run-on line to exaggerate my point.

funcName =
    -- otherFunc :: a -> m b -> _
    otherFunc arg1 do
        someOther arg2
        moreThings toDo

This, to me at a cursory glance, looks the same as the following:

funcName =
    -- otherFunc :: a -> b -> c -> d -> e -> f -> _
    otherFunc arg1 arg2
        someOther args
        moreThings toDo

So I’d just like a non-alphanumeric separator showing that it’s not just a bunch of arguments, so either the $ operator, or (...) parentheses to enclose the part. But I understand some people don’t have this issue, which is fine, but I just don’t like BlockArguments :man_shrugging:


P.S. And an example from this section from Typeclasses.com shows something I also dislike because it takes too long (for me) to understand the parsing order when seeing this at a glance:

{-# LANGUAGE BlockArguments #-}

import Control.Concurrent.Async (concurrently_)

blockStack =
  concurrently_
  do
    putStrLn ['a'..'z']
  do
    putStrLn ['A'..'Z']

The committee has voted, and (as things stand right now) GHC2024 will be GHC2021 plus

  • DataKinds
  • DerivingStrategies
  • DisambiguateRecordFields
  • ExplicitNamespaces
  • GADTs (and the implied MonoLocalBinds)
  • LambdaCase
  • RoleAnnotations

You can see the full tally at https://github.com/ghc-proposals/ghc-proposals/blob/joachim/ghc2024/proposals/0000-ghc2024.rst#2proposed-change-specification

13 Likes

Isn’t GADTs obsolete in light of ExistentialQuantification and TypeOperators, which are already enabled by GHC2021?

1 Like

The discussion here may assist: https://github.com/ghc-proposals/ghc-proposals/blob/joachim/ghc2024/proposals/0000-ghc2024.rst#10why-add-gadts-and-monolocalbinds

2 Likes

Honestly I wish RecordWildCards were banned and completely removed from GHC, RecordDotSyntax is strictly better in every aspects. I can understand why people felt the need for it at times but this should just be history now, it’s a terrible terrible extension.

4 Likes

Is ‘should now be history’ premature? RecordWildCards has been supported from GHC 6.8.1 but OverloadedRecordDot and OverloadedRecordUpdate are only supported from GHC 9.2.1 and the latter comes with an ‘EXPERIMENTAL’ warning in the documentation for GHC 9.2.1 to GHC 9.8.1.

Am I right to understand that, to get its full syntax benefits, moving from RecordWildCards to record dot syntax involves, in practice, the renaming of fields to remove the prefixes that identify the type? Taking my Stack code base example:

Step 1: just use a dot syntax:

-- | Interprets BuildOptsMonoid options.
buildOptsFromMonoid :: BuildOptsMonoid -> BuildOpts
buildOptsFromMonoid buildMonoid = BuildOpts
  { boptsLibProfile = fromFirstFalse
      (buildMonoid.buildMonoidLibProfile <>
       FirstFalse (if tracing || profiling then Just True else Nothing))
  ...
  -- 26 other fields relying on the 31 fields of a `BuildOptsMonoid` value
  ...
  , boptsDdumpDir = getFirst buildMonoid.buildMonoidDdumpDir
  }
 where
  ...

Step 2: also eliminate the use of prefixes in field names to identify the type:

-- | Interprets BuildOptsMonoid options.
buildOptsFromMonoid :: BuildOptsMonoid -> BuildOpts
buildOptsFromMonoid buildMonoid = BuildOpts
  { libProfile = fromFirstFalse
      (buildMonoid.libProfile <>
       FirstFalse (if tracing || profiling then Just True else Nothing))
  ...
  -- 26 other fields relying on the 31 fields of a `BuildOptsMonoid` value
  ...
  , ddumpDir = getFirst buildMonoid.ddumpDir
  }
 where
  ...

Currently, there are 118 instances of {..} in the Stack project: 39 in the pantry library and 79 in Stack itself. I am happy to work through eliminating all of them but I read the guidance in the GHC documention as saying, in terms, ‘hold off from doing that - things may change’.

1 Like

I disagree. For one, RecordDotSyntax is more magical. I had a record field with an existential forall, and I don’t think it worked with RecordDotSyntax. Plus, I view the whole syntax as experimental, since RecordDotUpdate doesnt work out of the box.

RecordWildCards is sometimes nice for initializing all fields, e.g. with QuickCheck:

a <- arbitrary
b <- arbitrary
pure X{..}

I’m pretty sure -Wall protects against missing a field, and this is protected against reordering fields, unlike the X <$> ... <*> ... syntax. RWCs isn’t too confusing for such a small function body.

Regardless how you feel about RWCs, it’s incorrect to say RecordDotSyntax is strictly better, there are definitely things RWCs can do that DotSyntax cant

8 Likes

Fine, it’s not strictly better in every aspect, it’s largely better in significant aspects on every use-case where they overlap. Now try to use (let alone refactor) any code-base where RecordWildCards is used and feel the pain. Typing 20 fields and using the type constructor is simply order of magnitudes more simple and readable in the definition of a record, using implicitly all the names that match in the context is a recipe for disaster. For instance if you add a field in the record that happens to already be in the context of some use-cases it’s simply impossible to detect, except for carefully examining every place you used it. Now of course I’m not saying this is likely to happen frequently, but just one time is too often.

All in all I’m not going to argue over this, I just experienced it first hand and I think advocating for using it is mostly a bad trade favoring initial code write over code maintenance. Which I think is a deep mistake but that’s just me.

Regarding the refactoring @mpilgrem, I think moving over to NamedFieldPuns is the correct thing to do for now. This is again, strictly better in every aspect where they overlap, except for a very small burden initially.

1 Like

You’ll have to pardon my ignorance on this matter - any mention of records in Haskell these days beyond a trivial remark usually results in me just ignoring it out of annoyance - but if you’re going to repeatedly write out all those fields and the associated constructor at each site of use…why bother with any form, style or variety of record notation at all? Just use regular pattern matching.

2 Likes

You could convince me that NamedFieldPuns is always better than RecordWildCards. But my main annoyance whenever people complain about RecordWildCards is that it’s almost always a non-issue with -Wall:

data Foo = Foo { x :: Int, y :: Int }

missingField :: Foo
missingField = Foo{..}
  where
    x = 1
    -- y = 2

shadowedVar :: Foo -> Int -> Int
shadowedVar Foo{..} x = x + y
error: [GHC-20125] [-Wmissing-fields, Werror=missing-fields]
    • Fields of ‘Foo’ not initialised:
        y :: Int
   |
   | missingField = Foo{..}
   |                ^^^^^^^

error: [GHC-10498]
    • Conflicting definitions for ‘x’
   |
   | shadowedVar Foo{..} x = x + y
   |                 ^^^^^
5 Likes

A small tip if @mpilgrem decides to go with this route: HLS features a code action for doing this translation automatically (disclaimer: I wrote it).

7 Likes

I think that would be very helpful in teaching beginners.

It would require a lot of courage (handling the backslash), but if GHC2024 defaulted to sane records using DuplicateRecordFields, OverloadedRecordDot and NoFieldSelectors it would really push the language far.

It takes enormous amount currently to figure out what combinations of extensions are needed to get dot style records.

Anything that breaks among these can be fixed and in the worst case codebases can avoid GHC2024 when needed.

19 Likes

Yeah, I think I said something similar in the GitHub thread, The only real argument against I can think of is that OverloadedRecordUpdate is still not fully implemented. Although whether that really causes any major issues, I’m unsure (it’s difficult to keep track of all the interactions between the record extensions, which is exactly a major reason why it would be nicer if a sensible set were on by default!).

Totally agree with this! This is a small change that could have a big impact for Haskell adoption going forward. Users expect dot style syntax from modern programming languages.

1 Like

Users also tend to expect simple I/O from modern programming languages, as the designers of Rust discovered - where’s the language extension/s for that?

It would require a lot of courage (handling the backslash), but if GHC2024 defaulted to sane records […]

Have you seen the GHC20xx criteria? One criteria is conservativeness, which is defined as backwards compatibility. You say it would be courageous to add these extensions, does that mean that you concede that it wouldn’t be conservative to add them?

Surely you agree that NoFieldSelectors would be a backwards incompatible change. It would be confusing to have a GHC20xx language edition that didn’t adhere to the process.

The ballot was already made, and votes were already cast. How does it makes any sense to propose new extensions for GHC2024 at this point? I think proposals like these are fundamentally unfit for the GHC20xx process since they require all text books to be rewritten, which is not feasible.

Was this post sponsored by Big Syntax?

7 Likes

You don’t need an extension for unsafePerformIO. :wink:

…but there’s still that matter of simplicity: