Skip to content

Instantly share code, notes, and snippets.

@mightybyte
Last active August 31, 2023 07:50
  • Star 17 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save mightybyte/6c469c125eb50e0c2ebf4ae26b5adfff to your computer and use it in GitHub Desktop.
A Taxonomy of Haskell Language Extensions

Haskell Language Extension Taxonomy

Caveat: It's just personal opinion, and was written to be a bit provocative and encourage discussion . It is also something that is constantly evolving. Some of the main criteria I used in constructing this taxonomy are age, how widely used it us, and how well understood it is by the average Haskell programmer. These things will change over time.

Aso, this is focused on appropriateness for use in commercial production code bases. If you are not thinking about commercial use with a team of multiple people, then the calculus completely changes.

Almost all extensions in this list can also be used with a "No" prefix. The notable ones for which "No" is most common are NoMonomorphismRestriction and NoImplicitPrelude.

This is not an exhaustive list of language extensions. As a general rule of thumb, extensions that are not mentioned here should be shied away from because either they weren't commonly used enough to be mentioned here, or they are very new. New language extenions should generally be avoided in production code. Ask not, "How can I use this great new GHC feature in my code?"...but rather ask, "How could I accomplish this in a clean way with the fewest language extensions?"

Here are some notable quotes on this topic from prominent haskellers.

I try to stay with Haskell 2010 as far as I can. The one exception is MPTC and FD, which are sometimes too useful to avoid. Furthermore, I use extensions to Haskell 2010 that I regard as just syntactic sugar, e.g., TupleSections. What I try to avoid is extensions that are a semantic headache, e.g., GADTs.

Don't get me wrong, I like cool features as much as anyone else (I suggested GADTs 10 years before GHC got them), but for production code I think it's best to stay very conservative and avoid too much cleverness.

-- Lennart Augustsson, author of multiple languages and Haskell compilers

Stick to the basics. You get the vast majority of benefits. I haven't talked about GADTs. I haven't talked about any technology invented in the last 10 years... You get most of the benefit just using newtypes, data, and functions. And certainly all the benefits of reuse and minimizing complexity were already there in '95 or earlier.

-- Don Stewart Haskell in the Large October 2016 (55:55)

Language Pragma History https://ghc.haskell.org/trac/ghc/wiki/LanguagePragmaHistory

Another take on this topic http://dev.stephendiehl.com/hask/#language-extensions

Level 0 - Use at will

Tried and true extensions. Very unlikely to cause problems. You can use these and rarely get any complaints.

  • ApplicativeDo
  • AutoDeriveTypeable -- Automatic derivation of Typeable
  • BangPatterns
  • Cpp
  • DeriveDataTypeable
  • DeriveFoldable
  • DeriveFunctor
  • DeriveGeneric
  • DeriveLift
  • DeriveTraversable
  • EmptyDataDecls
  • ExistentialQuantification
  • ExplicitForAll
  • FlexibleContexts
  • FlexibleInstances
  • ForeignFunctionInterface
  • GeneralizedNewtypeDeriving
  • JavaScriptFFI
  • KindSignatures
  • LambdaCase
  • MagicHash
  • MonomorphismRestriction
  • MultiParamTypeClasses
  • OverloadedLists
  • OverloadedStrings
  • PackageImports
  • PartialTypeSignatures
  • RankNTypes
  • RecordWildCards -- This one is somewhat controversial
  • ScopedTypeVariables
  • StandaloneDeriving
  • TupleSections
  • TypeApplications
  • UnboxedTuples
  • ViewPatterns

Level 1 - More complex, but well understood

More complexity than level 0, but on the whole quite well understood and useful. Pose a little bit more of an obstacle in terms of concepts you need to understand to use them. Implementations without level 1 are still preferred if possible.

  • AllowAmbiguousTypes
  • DefaultSignatures
  • DerivingStrategies
  • FunctionalDependencies
  • GADTs
  • ImplicitPrelude -- Alternate preludes meh
  • QuasiQuotes
  • RecursiveDo
  • TemplateHaskell
  • TypeFamilies
  • TypeOperators
  • TypeSynonymInstances
  • UndecidableInstances

Level 2 - Avoid unless you have a VERY good reason

Substantially newer and more complex concepts that are not in general production ready. Don't use unless you have an alternate implementation for comparison that doesn't use it, and can clearly articulate the value you are getting from using the extension and have weighed it against the cost.

  • Arrows -- Arrow-notation syntax. Arrows suck :)
  • ConstraintKinds
  • DataKinds
  • DeriveAnyClass
  • OverlappingInstances
  • PolyKinds
  • RebindableSyntax
  • Strict
  • StrictData
  • TypeFamilyDependencies
  • TypeInType

Banned

I don't think there are very many absolutes in software and this is no exception. You should interpret this as "banned for the most part".

  • ImpredicativeTypes
  • IncoherentInstances
  • UnicodeSyntax -- Admittedly opinionated, USA-centric, and pragmatically intended for maximum ease of use across large teams.

Unknown / undecided

  • AlternativeLayoutRule
  • AlternativeLayoutRuleTransitional
  • BinaryLiterals
  • CApiFFI
  • ConstrainedClassMethods
  • DatatypeContexts
  • DisambiguateRecordFields
  • DoAndIfThenElse
  • DuplicateRecordFields
  • EmptyCase
  • ExplicitNamespaces
  • ExtendedDefaultRules -- Use GHC's extended rules for defaulting
  • GADTSyntax
  • GHCForeignImportPrim
  • ImplicitParams
  • InstanceSigs
  • InterruptibleFFI
  • LiberalTypeSynonyms
  • MonadComprehensions
  • MonadFailDesugaring
  • MonoLocalBinds
  • MonoPatBinds
  • MultiWayIf
  • NPlusKPatterns
  • NamedWildCards
  • NegativeLiterals
  • NondecreasingIndentation
  • NullaryTypeClasses
  • NumDecimals
  • OverloadedLabels
  • ParallelArrays -- Syntactic support for parallel arrays
  • ParallelListComp
  • PatternGuards
  • PatternSynonyms
  • PostfixOperators
  • RecordPuns
  • RelaxedLayout
  • RelaxedPolyRec -- Deprecated
  • RoleAnnotations
  • StaticPointers
  • TemplateHaskellQuotes -- subset of TH supported by stage1, no splice
  • TraditionalRecordSyntax
  • TransformListComp
  • UnboxedSums
  • UndecidableSuperClasses
  • UnliftedFFITypes
@fosskers
Copy link

fosskers commented Aug 15, 2018

DuplicateRecordFields combined with generic-lens is one solution to the "record problem". Example usage.

This approach also re-enables one to use the nicest possible names for fields, then auto-generate JSON codecs as-is via the usual FromJSON and ToJSON.

@mightybyte
Copy link
Author

I don't think that is a solution to the "record problem" because those names conflict with names that should be used for things of smaller scope. The "record problem" is not actually a problem with Haskell. It only exists for people who insist on using bad names because they unreasonably assume that they can import naming practices inappropriately from the OO world to Haskell. Make name complexity proportional to scope and the problem completely disappears. (This is not just a Haskell thing or even just a programming thing--it is a universal law of nature. If your names are not complex enough for their scope, you will have collisions.) It is simple, monomorphic, preserves type inference, and has worked fine in Haskell for the last 20 years.

@ysangkok
Copy link

Missing BlockArguments on here. It's kinda meta-controversial to me since some will say "syntax doesn't matter, whatever", and others will say "it's the default in PureScript! Obviously not controversial".

@ysangkok
Copy link

ysangkok commented Sep 27, 2022

Interesting how this differs from GHC2021:

  • ConstraintKinds got 11 out of 11 votes, so doesn't seem controversial at all, but here, it is listed as "Level 2 - Avoid unless you have a VERY good reason". A stark difference.
  • PolyKinds got 9 out of 11 votes, so not very controversial. Also level 2 in this document.

There are some undecided extensions here that there is committee consensus on (judging by how they are in GHC2021):

  • NPlusKPatterns was removed from Haskell2010, I think most people are happy with it gone.
  • GADTSyntax got 11 out of 11 votes.
  • EmptyCase got 11 out of 11 votes.
  • ConstrainedClassMethods got 10 out of 11 votes.
  • InstanceSigs got 10 out of 11 votes.
  • NamedWildCards got 8 out of 11 votes.
  • PatternGuards is part of Haskell2010 and therefore included.
  • RelaxedPolyRec is part of Haskell2010 and therefore included.
  • DoAndIfThenElse is part of Haskell2010 and therefore included.

Some extensions are not listed but adopted in GHC2021:

  • ImportQualifiedPost got 10 out of 11 votes.
  • PostfixOperators got 10 out of 11 votes.
  • StandaloneKindSignatures got 9 out of 11 votes.
  • NamedFieldPuns got 9 out of 11 votes.
  • TupleSections got 8 out of 11 votes.

Extensions that are level 0 here but not in GHC2021:

  • OverloadedStrings (7 out of 11 votes)
  • RecordWildCards (7 out of 11 votes)
  • LambdaCase (6 out of 11 votes)
  • ViewPatterns (5 out of 11 votes)
  • OverloadedLists (4 out of 11 votes)
  • PackageImports (1 out of 11 votes)
  • MagicHash (1 out of 11 votes)
  • UnboxedTuples (1 out of 11 votes)
  • CPP (0 out of 11 votes)

@AriFordsham
Copy link

@ysangkok I think part of the explanation is: GHC2021 defines which extensions are relatively benign to enable. If something for example makes the type system more powerful without weakening inference, why not enable it? Whereas this list is about extensions that are sensible to use.

@mightybyte
Copy link
Author

@ysangkok I'm not a fan of BlockArguments because it's a rather new extension and it increases the mental load required to read the code. The example given here is easily handled by using a $ instead of parentheses. Good software isn't about minimizing the number of characters you have to type, it's about making the code as easy as possible to understand and maintain in the future.

As far as the comparison to GHC2021, thanks for compiling all that information here! It's an interesting comparison. @AriFordsham's comment is pretty close to my view. Case in point: CPP. It's level 0 in my list because when you need it you need it. It's well understood. None of the real world CPP I've seen makes any developer say "I don't understand what this code is doing." It's there most often because you need to make something build across multiple GHC versions and there are plenty of situations where that's exactly what you need to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment