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.