Skip to content
AgileLogics
6 min read

Language Extensions

Prerequisites: Type classes

GHC implements far more than the Haskell 2010 standard. Extra features are switched on with language extensions, either per file:

{-# LANGUAGE LambdaCase, RecordWildCards #-}

or for a whole package with default-extensions in the .cabal file.

Language editions

Rather than listing dozens of extensions, choose an edition: GHC2021 (the default when you run ghc directly since GHC 9.2) enables about 40 well-established ones, including BangPatterns, TupleSections, ScopedTypeVariables, TypeApplications, DeriveFunctor, and GeneralizedNewtypeDeriving. GHC2024 adds LambdaCase, DataKinds, GADTs, and a few more. Set it with default-language: GHC2024 in cabal.

A tour of everyday extensions

{-# LANGUAGE LambdaCase, MultiWayIf, RecordWildCards, OverloadedStrings #-}

import qualified Data.Text as T
import qualified Data.Text.IO as TIO

data User = User {name :: T.Text, age :: Int}

describe :: Maybe Int -> String
describe = \case
  Nothing -> "unknown"
  Just n -> "value " ++ show n

size :: Int -> String
size n = if | n < 10 -> "small" | n < 100 -> "medium" | otherwise -> "large"

greet :: User -> T.Text
greet User {..} = "Hi " <> name <> ", age " <> T.pack (show age)

main :: IO ()
main = do
  putStrLn (describe (Just 3))
  putStrLn (size 42)
  TIO.putStrLn (greet (User "Ada" 36))
  print (map (,True) [1, 2 :: Int])
  print (read @Int "42", show @Double 1)
value 3
medium
Hi Ada, age 36
[(1,True),(2,True)]
(42,"1.0")
Extension What it gives you
LambdaCase \case — a lambda that pattern matches its argument
MultiWayIf `if
RecordWildCards User {..} brings every field into scope
OverloadedStrings String literals as Text, ByteString, ...
TupleSections (,True) means \x -> (x, True)
TypeApplications read @Int passes a type explicitly
DerivingStrategies, DerivingVia Control how instances are derived
OverloadedRecordDot user.name field access

TupleSections and TypeApplications needed no pragma above, because they're in GHC2021.

Deriving strategies

{-# LANGUAGE DerivingStrategies, DerivingVia #-}

import Data.Monoid (Sum (..))

newtype Score = Score Int
  deriving stock (Show)
  deriving newtype (Eq, Ord, Num)
  deriving (Semigroup, Monoid) via (Sum Int)

main :: IO ()
main = do
  print (Score 3 + Score 4)
  print (mconcat [Score 10, Score 20, Score 5])
  print (maximum [Score 2, Score 9])
Score 7
Score 35
Score 9

deriving via borrows the instance of another type with the same representation — here, "combine scores by adding them".

Use extensions deliberately

Stick to well-known, stable extensions. Some, like IncoherentInstances, weaken the guarantees you rely on; if an extension's documentation warns about it, avoid it unless you understand exactly why you need it.