How to learn language extensions

I’m relatively new to Haskell (although I was a programmer for 39 years), and I find the way some language extensions are documented to be rather opaque.

Let’s take a practical example. I wanted to contribute to a Haskell project (nameless, to protect the innocent) and the first module I opened up used:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

(other modules used other extensions). I hate guesswork, so I set about reading the docs for the unfamiliar extensions.

Flexible contexts didn’t seem too threatening, except that the docs were for GHC 9.0.1 and the latest FlexibleContexts docs are much terser and I have no idea why. The older docs were in a section that pointed at the paper Type classes: exploring the design space - a challenging read in itself.

Type families looked harder still. The docs linked to three academic papers and then extended over several pages. The Haskell wiki page on type families was a little gentler, but still pretty off-putting.

I dare say if I regarded getting up to speed on each of these extensions as a project in its own right, I could eventually become sufficiently confident that I would then know how to work with the module in question.

I’m interested in how others approach learning language extensions.

(This topic originated in a thread on maintainability.)

9 Likes

One way to go about this is to let your IDE/compiler help you. Comment out a single extension pragma. See where your IDE/compiler complains. Look at that specific code and think about what it is trying to do in the context of documentation on the extension.

11 Likes

Same thing when writing new code: see what pragma the IDE/compiler recommends - then read about that extension in the context of your code.

3 Likes

I recommend this section of What I Wish I Knew When Learning Haskell: https://github.com/sdiehl/wiwinwlh/blob/master/tutorial.md#language-extensions

(Sidenote: Haskell has some really great documentation and resources, but they can be frustratingly hard to find; I think it would be great if What I Wish I Knew When Learning Haskell were maintained as the intermediate section of a canonical Haskell guide, directly linked to via a big red button on the frontpage of haskell.org.)

8 Likes

One of the biggest issues with the documentation on GHC extensions is the academia-inspired attitude “what are they” instead of “what use cases they can help with”.

There is probably only one definition for every extension (“what it is”), and hundreds of possible usages in hundreds of surprising contexts (“how and why using it”). This becomes even harder when several extensions start influencing the code simultaneously producing weird, strange and implicit behavior.

The documentation is too abstract and detached. It may be useful for the compiler developers but it has low applicability to the real projects. The examples are too vague (if any), the issues coming with an extension are unclear. Worse, if it’s a paper.

Learning Haskell’s extensions is always a trial and error process with the code and the compiler. Nice if it tells you what extension to enable, but you have no idea why, and what are the implications of it mid- and long term.

In other words, there are no best practices, there is no good documentation, and there are almost no showcases for any relatively difficult extension. Elaborating all this requires skills that are not quite spread in the community. Yes, writing good documentation is difficult.

12 Likes

:seti in ghci has probably been the biggest source of Haskell extension learning for me

2nd place was the servant paper

I haven’t ever really found articles or blogs about the “why” useful. Too opinion based. Haskell makes code structure so real that working with the real thing is better learning than listening to someone else who claims to know better.

The GHC user guide is great though. I am glad it isn’t littered with in-the-small programming. A parallel official document that doesn’t pollute it would be useful though. A “field guide”?

2 Likes

Not true.

Opinions and best practices are mandatory for a community that wants to be well-informed and well-prepared to do real stuff.

Existence of various opinions, even if they contradict to each other is much better than trying to explore things again and again. It’s cheaper for the industry, it’s clearer from the strategic point of view (benefits vs tradeoffs), it provides ideas and pushes the engineering discipline further.

8 Likes

Two useful links:

7 Likes

Not exactly extension documentation, but a good read on the topic is An opinionated guide to Haskell in 2018

2 Likes

I think “untrue” is a little too far. This is all opinion. And my opinion is I’ve spent a decade learning Haskell and ignoring the loudest people works best.

I read the doc, ask questions on reddit and there if the doc is not enough and maybe try to use them in a dummy project. But that’s been a hobby of mine for years…

About your practical experience, I would say it’s even easier. If you are finding new extensions in a project : just look at the code using those extensions. No need to find example in net it’s there in frontnof you. Alternativley, chances are you can ignore then alltogether until you stumple upon them and once again, just rewd the code (and the doc :slight_smile: )

3 Likes

Ah, I see. The older version mentions " the extension that adds a kind of constraints,". Constraint Kinds got taken out as too exotic. There’s now talk of putting it back in (probably a little re-designed), because some people miss it. Before you ask: yes there is too much ‘thrash’ in extensions. And yes it’s confusing for newbies/probably confusing for everybody. PatternSignatures is in process of getting put back in, having gotten removed somewhere around 2008 – except there’s some fishhooks in today’s GHC.

The paper ‘exploring the design space’ is now linked from here. That’s an example of the (probably sensible) restructuring that’s left me disoriented. OTOH that paper is now very old, and probably more notable for what it doesn’t mention but has arrived since.

The trouble (for me answering that q) is I learnt each of those extensions piecemeal as they arrived. (And typically they arrived in chunks over several releases. TypeFamilies was particularly agonising. That’s the “three academic papers” – but then it’s a big topic.)

FlexibleContexts seems pretty trivial compared to what’s in front of you (sorry). I can’t now remember whether I found anything difficult about it. FlexibleInstances – which you’ll bump into soon – also seems much of a muchness. It’s what it leads on to hand-in-hand with FlexibleConstraints that goes into the quagmire of OverlappingInstances – except that’s now deprecated as such.

Yeah. Each extension is written-up as if it’s orthogonal to the others. But some intersect/interact with each other in non-obvious ways. And it’s nobody’s job to document that interaction. Overlaps/TypeFamilies/FunDeps is a viper’s nest. What’s more GHC allows you to write instance decls that don’t make sense together, but you only find out in some method call at a module in a distant galaxy.

2 Likes

Oh, I didn’t know about that. But they’re already back and even in GHC2021 now.

1 Like

Just to notice, I’m writing a book “Pragmatic Type-Level Design”. It doesn’t have a goal to cover Haskell’s extensions, but it will do anyway for many because type-level metaprogramming can’t be done without them. My book is not a replacement for the documentation, but it will definitely answer “why” and “when” questions (ignoring the “what it is” question completely).

Need to say, “Haskell in Depth” by Vitaly Bragilevsky would be the first material to look at for learning the extensions.

4 Likes

Gnaa. I should probably give up trying to follow GHC. (Newbies ignore the following: too much information.)

  • It’s Kind constraints that got removed. Of course ConstraintKinds are something completely different; how could I possibly have confused them?

  • The difference in the two versions of the docos that @Underlap points out are more reshuffling of sections: FlexibleContexts are now in a Section 6.10 on Constraints. The older text on FlexibleContexts has been split, 6.10.1 with 6.10.3 on ConstraintKinds. So no material got disappeared.

  • FlexibleContexts previously was discussed under 6.8 Class and Instance decls. Now it’s true that (Flexible) Contexts appear all over the place, not just on Classes/Instances. OTOH if you’re trying to read the docos like a book, talking about Contexts after Classes/Instances is rather a strain on the attention span. Could do with a forward procedure (for those who remember ALGOL).

  • I think this just underlines my earlier point that each feature is documented orthogonally, even though they interact.

@Underlap / all learners: I suggest you identify which version of GHC your project is using and read the docos only for that version. It’s bad enough dealing with churn in extensions/functionality. Churn in User Guide structure is noise you can do without.

2 Likes

On this topic, I’ve long liked Stephen Diehl’s What I Wish I Knew When Learning Haskell (archived link). When I learnt Haskell it was an invaluable resource for all different kinds of Haskell features, but especially languages extensions. Unfortunately, it’s now unmaintained and no longer accessible at its original location. (And the license forbids ‘reauthoring’, though what that means I’m not quite sure.)

1 Like

Ask ChatGPT

1 Like

ChatGPT may be indeed helpful for learning. However, these answers are not great, it’s just a bunch of strange words that rely on other strange words, and do not explain anything. What does it even mean to be flexible in this context? Constraints? What are they and what problems do they solve? Why flexible constraints are better than rigid, and in what situations? Flexible instances enable flexible constraints. A circular explanation. This is the issue with the official documentation, too.

2 Likes
  • Sepulka – pl.: sepulkas, a prominent element of the civilization of Ardrites from the planet of Enteropia; see “Sepulkaria
  • Sepulkaria – sing: sepulkarium, establishments used for sepuling; see “Sepuling
  • Sepuling – an activity of Ardrites from the planet of Enteropia; see “Sepulka

– Stanisław Lem

6 Likes

When programming in Haskell, you do not read the documentation — you trust the compiler and rush forward with your eyes fixed on the goal, like an anime character.

bad news

Trying to to understand what extensions do is impossible, because trying to understand how GHC works is impossible to begin with. For example, there is no way you can emulate GHC’s type inference with pen and paper — it is too complicated, and changes in subtle ways over time. I asked about it some time ago because I wanted to do fancy type level programming and I could not understand why something is not working. Here is what @nomeata told me:

Ignat Is there a comprehensive description of the type inference rules used by the current GHC ?

Joachim No, unfortunately not. It’s spread over a bunch of papers, and never complete. I discussed this with Richard [I guess @rae Eisenberg] recently, and yet, would be good to have something like that!

Some extensions remove restrictions that turned out to be needless — say FlexibleContexts. Trying to understand them means digging into the motivation of the people who were working on GHC 20 or 30 years ago — a job for a historian.

Other extensions add features that are often half baked and experimental. For example:

{-# language FunctionalDependencies, TypeFamilyDependencies, UnicodeSyntax #-}

class X a b c | a b → c, c b → a

type family F a b = c | c b → a

These two type level spells should do the same. But the latter will not work. The article that introduced type family dependencies — Injective Type Families for Haskell by Jan Stolarek, Simon @simonpj and Richard @rae — only has this to say:

The check that the injectivity annotation is sound would be an extension of Definitions 2 and 8, and the improvement rule would mimic the one for functional dependencies. However, this remains as future work: we have not yet extended the metatheory or implementation to accommodate it.

Injective Type Families for Haskell, section 7.2.

Maybe in the future we shall have an extension FlexibleTypeFamilyDependencies that would amend this. But there is a lot of issues like this with the newer features of Haskell, when something that should be working in principle turns out not to work in practice.

It is impossible to understand how GHC executes your program, as well. Sometimes it will let float something and turn your logarithmic complexity into polynomial, or something such. I thought I understood after I read Simon @simonpj 's book Implementing Functional Languages, but Lennart @augustss was quick to point out that my understanding is naïve and incomplete.

good news

However, even though I understand Haskell so poorly, I am unafraid to tackle any code base.

In some other languages, like say C or JavaScript, documentation is the only way to understand how something should be working, and there are subtle issues at every step of the way that you must keep in mind. Constant vigilance. The habit of thoroughly reading the relevant documentation would have served me well — when I try to write something in C it either segmentation faults or at best silently does nothing.

But Haskell is not like this. You can trust it. If it compiles, it works. You only need to watch out for ill-founded recursion and be aware of dangers associated with asynchronous IO. Other than that, Haskell will take care of you.

The hard task is to get your fancily typed program to compile. But this is a kind of task you should work on when you do already have a fancily typed program on your hands that does not compile. There are a lot of people here (say me) who will be eager to help you out. Beyond the grasp of a mere human Haskell may be, but as a hive mind we can always figure it out. Bring the code, and we shall fix it.

11 Likes