How to learn language extensions

My general heuristic is:

  1. Use GHC_20XX
  2. Besides the above, use as few language extensions as possible
  3. Don’t worry about it, just code and if you end up needing one, you’ll find out while you’re working.

The only language extensions I’ve explicitly even bothered using or learning about are OverloadedStrings, DuplicateRecordFields, RecordDotSyntax.

There’s others I use, but I only use them if my code breaks without them. HLS has never really steered me wrong on stuff I need to enable, and I don’t bother reading about them.

1 Like

Whoa, are those extensions not harmless? I always thought they were strictly additive; they let you write more things than before, but if you don’t want to write those things they do nothing. What’s the downside to always having them on?

in that sense you are right: those two examples are not misleading.

BUT for a beginner, it still can happen that you stumble into rank-N-types or type families while it’s simply not recommendable to wield such tools, unless you know what you are doing.

2 Likes

RankNTypes is strictly additive. (I believe.)

GADTs implies MonoLocalBinds (to give predictable type inference). That in turn can make some, relatively obscure, program fail.

You can say -XGADTs -XNoMonoLocalBinds, which is strictly additive. In exchange, you may get slightly less predictable type inference (search for “Let should not be generalised”); but that said, I don’t think I’ve ever seen a bug report which turned out to be down to this unpredictability.

1 Like

FWIW I found these series of videos by @rae to be helpful in understanding a few extensions - https://www.youtube.com/playlist?list=PLyzwHTVJlRc9QcF_tdqc9RdxJED8Mvyh1

2 Likes

MonoLocalBinds is a bugbear of mine, so here’s my take on it.

GADTs implies MonoLocalBinds (to give predictable type inference). That in turn can make some, relatively obscure, program fail.

I wouldn’t call the all the programs that fail when MonoLocalBinds is enabled “relatively obscure”. Here’s a simple one:

f :: Bool -> ReadP (Char, String)
f expectWhitespace =
  (,) <$> g get (char '\n') <*> g look (string "\n\n")
  where
    g x y = if expectWhitespace then x <* y else x

There are a couple of problems that make the problem with MonoLocalBinds particularly acute:

  • The error message doesn’t give any clue that MonoLocalBinds is the problem, and that adding a fully general type signature is the required solution. In Haskell we’re used to local definitions having their types generalised (notwithstanding some paper titles to the contrary)!
  • Even if I did know that, the fully general type siganture can be quite long and require importing additional types to even write at all (here it’s just g :: ReadP a -> ReadP b -> ReadP a so it’s not terrible but not great).

The error message:

test18.hs:7:33: error: [GHC-83865]
    • Couldn't match type ‘Char’ with ‘[Char]’
      Expected: ReadP String
        Actual: ReadP Char
    • In the second argument of ‘(<*>)’, namely
        ‘g look (string "\n\n")’
      In the expression:
        (,) <$> g get (char '\n') <*> g look (string "\n\n")
      In an equation for ‘f’:
          f expectWhitespace
            = (,) <$> g get (char '\n') <*> g look (string "\n\n")
            where
                g x y = if expectWhitespace then x <* y else x
  |
7 |   (,) <$> g get (char '\n') <*> g look (string "\n\n")
  |                                 ^^^^^^^^^^^^^^^^^^^^^^

test18.hs:7:35: error: [GHC-83865]
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected: ReadP Char
        Actual: ReadP String
    • In the first argument of ‘g’, namely ‘look’
      In the second argument of ‘(<*>)’, namely ‘g look (string "\n\n")’
      In the expression:
        (,) <$> g get (char '\n') <*> g look (string "\n\n")
  |
7 |   (,) <$> g get (char '\n') <*> g look (string "\n\n")
  |                                   ^^^^

test18.hs:7:41: error: [GHC-83865]
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected: ReadP Char
        Actual: ReadP String
    • In the second argument of ‘g’, namely ‘(string "\n\n")’
      In the second argument of ‘(<*>)’, namely ‘g look (string "\n\n")’
      In the expression:
        (,) <$> g get (char '\n') <*> g look (string "\n\n")
  |
7 |   (,) <$> g get (char '\n') <*> g look (string "\n\n")
  |                                         ^^^^^^^^^^^^^
2 Likes

I wouldn’t call the all the programs that fail when MonoLocalBinds is enabled “relatively
obscure”. Here’s a simple one:

Agree; I also have had quite reasonable programs (I think!) fail because of this, and it’s only chance that I happened to remember this was the culprit.

2 Likes

Having concrete examples is very helpful – thank you. I don’t disagree with anything you say. I certainly agree that it would be great to find a better solution. I just don’t know of one. (Except switching off MonoLocalBinds and crossing your fingers.)

The type inference difficulty comes when there are free type variables in the environment. Interestingly, in your example, there are none, because expectWhiteSpace is known to have type Bool. So one could imagine making MonoLocalBinds not apply in such cases. But I wonder if that is an accident of your example – I expect so.

The type inference difficulty comes when there are free type variables in the environment. Interestingly, in your example, there are none, because expectWhiteSpace is known to have type Bool. So one could imagine making MonoLocalBinds not apply in such cases.

Yes, I think that would be a good idea. We discussed it briefly before.