Common GHC Language Extensions
Remarks#
These language extensions are typically available when using the Glasgow Haskell Compiler (GHC) as they are not part of the approved Haskell 2010 language Report. To use these extensions, one must either inform the compiler using a flag or place a LANGUAGE
programa before the module
keyword in a file. The official documentation can be found in section 7 of the GCH users guide.
The format of the LANGUAGE
programa is {-# LANGUAGE ExtensionOne, ExtensionTwo ... #-}
. That is the literal {-#
followed by LANGUAGE
followed by a comma separated list of extensions, and finally the closing #-}
. Multiple LANGUAGE
programas may be placed in one file.
MultiParamTypeClasses
It’s a very common extension that allows type classes with multiple type parameters. You can think of MPTC as a relation between types.
{-# LANGUAGE MultiParamTypeClasses #-}
class Convertable a b where
convert :: a -> b
instance Convertable Int Float where
convert i = fromIntegral i
The order of parameters matters.
MPTCs can sometimes be replaced with type families.
FlexibleInstances
Regular instances require:
All instance types must be of the form (T a1 ... an)
where a1 ... an are *distinct type variables*,
and each type variable appears at most once in the instance head.
That means that, for example, while you can create an instance for [a]
you can’t create an instance for specifically [Int]
.; FlexibleInstances
relaxes that:
class C a where
-- works out of the box
instance C [a] where
-- requires FlexibleInstances
instance C [Int] where
OverloadedStrings
Normally, string literals in Haskell have a type of String
(which is a type alias for [Char]
). While this isn’t a problem for smaller, educational programs, real-world applications often require more efficient storage such as Text
or ByteString
.
OverloadedStrings
simply changes the type of literals to
"test" :: Data.String.IsString a => a
Allowing them to be directly passed to functions expecting such a type. Many libraries implement this interface for their string-like types including Data.Text and Data.ByteString which both provide certain time and space advantages over [Char]
.
There are also some unique uses of OverloadedStrings
like those from the Postgresql-simple library which allows SQL queries to be written in double quotes like a normal string, but provides protections against improper concatenation, a notorious source of SQL injection attacks.
To create a instance of the IsString
class you need to impliment the fromString
function. Example†:
data Foo = A | B | Other String deriving Show
instance IsString Foo where
fromString "A" = A
fromString "B" = B
fromString xs = Other xs
tests :: [ Foo ]
tests = [ "A", "B", "Testing" ]
† This example courtesy of Lyndon Maydwell (sordina
on GitHub) found here.
TupleSections
A syntactic extension that allows applying the tuple constructor (which is an operator) in a section way:
(a,b) == (,) a b
-- With TupleSections
(a,b) == (,) a b == (a,) b == (,b) a
N-tuples
It also works for tuples with arity greater than two
(,2,) 1 3 == (1,2,3)
Mapping
This can be useful in other places where sections are used:
map (,"tag") [1,2,3] == [(1,"tag"), (2, "tag"), (3, "tag")]
The above example without this extension would look like this:
map (\a -> (a, "tag")) [1,2,3]
UnicodeSyntax
An extension that allows you to use Unicode characters in lieu of certain built-in operators and names.
ASCII | Unicode | Use(s) |
---|---|---|
:: |
∷ |
has type |
-> |
→ |
function types, lambdas, case branches, etc. |
=> |
⇒ |
class constraints |
forall |
∀ |
explicit polymorphism |
<- |
← |
do notation |
* |
★ |
the type (or kind) of types (e.g., Int :: ★ ) |
>- |
⤚ |
proc notation for Arrows |
-< |
⤙ |
proc notation for Arrows |
>>- |
⤜ |
proc notation for Arrows |
-<< |
⤛ |
proc notation for Arrows |
For example:
runST :: (forall s. ST s a) -> a
would become
runST ∷ (∀ s. ST s a) → a
Note that the *
vs. ★
example is slightly different: since *
isn’t reserved, ★
also works the same way as *
for multiplication, or any other function named (*)
, and vice-versa. For example:
ghci> 2 ★ 3
6
ghci> let (*) = (+) in 2 ★ 3
5
ghci> let (★) = (-) in 2 * 3
-1
BinaryLiterals
Standard Haskell allows you to write integer literals in decimal (without any prefix), hexadecimal (preceded by 0x
or 0X
), and octal (preceded by 0o
or 0O
). The BinaryLiterals
extension adds the option of binary (preceded by 0b
or 0B
).
0b1111 == 15 -- evaluates to: True
ExistentialQuantification
This is a type system extension that allows types that are existentially quantified, or, in other words, have type variables that only get instantiated at runtime†.
A value of existential type is similar to an abstract-base-class reference in OO languages: you don’t know the exact type in contains, but you can constrain the class of types.
data S = forall a. Show a => S a
or equivalently, with GADT syntax:
{-# LANGUAGE GADTs #-}
data S where
S :: Show a => a -> S
Existential types open the door to things like almost-heterogenous containers: as said above, there can actually be various types in an S
value, but all of them can be show
n, hence you can also do
instance Show S where
show (S a) = show a -- we rely on (Show a) from the above
Now we can create a collection of such objects:
ss = [S 5, S "test", S 3.0]
Which also allows us to use the polymorphic behaviour:
mapM_ print ss
Existentials can be very powerful, but note that they are actually not necessary very often in Haskell. In the example above, all you can actually do with the Show
instance is show (duh!) the values, i.e. create a string representation. The entire S
type therefore contains exactly as much information as the string you get when showing it. Therefore, it is usually better to simply store that string right away, especially since Haskell is lazy and therefore the string will at first only be an unevaluated thunk anyway.
On the other hand, existentials cause some unique problems. For instance, the way the type information is “hidden” in an existential. If you pattern-match on an S
value, you will have the contained type in scope (more precisely, its Show
instance), but this information can never escape its scope, which therefore becomes a bit of a “secret society”: the compiler doesn’t let anything escape the scope except values whose type is already known from the outside. This can lead to strange errors like Couldn't match type ‘a0’ with ‘()’ ‘a0’ is untouchable
.
†Contrast this with ordinary parametric polymorphism, which is generally resolved at compile time (allowing full type erasure).
Existential types are different from Rank-N types – these extensions are, roughly speaking, dual to each other: to actually use values of an existential type, you need a (possibly constrained-) polymorphic function, like show
in the example. A polymorphic function is universally quantified, i.e. it works for any type in a given class, whereas existential quantification means it works for some particular type which is a priori unknown. If you have a polymorphic function, that’s sufficient, however to pass polymorphic functions as such as arguments, you need {-# LANGUAGE Rank2Types #-}
:
genShowSs :: (∀ x . Show x => x -> String) -> [S] -> [String]
genShowSs f = map (\(S a) -> f a)
LambdaCase
A syntactic extension that lets you write \case
in place of \arg -> case arg of
.
Consider the following function definition:
dayOfTheWeek :: Int -> String
dayOfTheWeek 0 = "Sunday"
dayOfTheWeek 1 = "Monday"
dayOfTheWeek 2 = "Tuesday"
dayOfTheWeek 3 = "Wednesday"
dayOfTheWeek 4 = "Thursday"
dayOfTheWeek 5 = "Friday"
dayOfTheWeek 6 = "Saturday"
If you want to avoid repeating the function name, you might write something like:
dayOfTheWeek :: Int -> String
dayOfTheWeek i = case i of
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
Using the LambdaCase extension, you can write that as a function expression, without having to name the argument:
{-# LANGUAGE LambdaCase #-}
dayOfTheWeek :: Int -> String
dayOfTheWeek = \case
0 -> "Sunday"
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6 -> "Saturday"
RankNTypes
Imagine the following situation:
foo :: Show a => (a -> String) -> String -> Int -> IO ()
foo show' string int = do
putStrLn (show' string)
putStrLn (show' int)
Here, we want to pass in a function that converts a value into a String, apply that function to both a string parameter and and int parameter and print them both. In my mind, there is no reason this should fail! We have a function that works on both types of the parameters we’re passing in.
Unfortunately, this won’t type check! GHC infers the a
type based off of its first occurrence in the function body. That is, as soon as we hit:
putStrLn (show' string)
GHC will infer that show' :: String -> String
, since string
is a String
. It will proceed to blow up while trying to show' int
.
RankNTypes
lets you instead write the type signature as follows, quantifying over all functions that satisfy the show'
type:
foo :: (forall a. Show a => (a -> String)) -> String -> Int -> IO ()
This is rank 2 polymorphism: We are asserting that the show'
function must work for all a
s within our function, and the previous implementation now works.
The RankNTypes
extension allows arbitrary nesting of forall ...
blocks in type signatures. In other words, it allows rank N polymorphism.
OverloadedLists
added in GHC 7.8.
OverloadedLists, similar to OverloadedStrings, allows list literals to be desugared as follows:
[] -- fromListN 0 []
[x] -- fromListN 1 (x : [])
[x .. ] -- fromList (enumFrom x)
This comes handy when dealing with types such as Set
, Vector
and Map
s.
['0' .. '9'] :: Set Char
[1 .. 10] :: Vector Int
[("default",0), (k1,v1)] :: Map String Int
['a' .. 'z'] :: Text
IsList
class in GHC.Exts
is intended to be used with this extension.
IsList
is equipped with one type function, Item
, and three functions, fromList :: [Item l] -> l
, toList :: l -> [Item l]
and fromListN :: Int -> [Item l] -> l
where fromListN
is optional. Typical implementations are:
instance IsList [a] where
type Item [a] = a
fromList = id
toList = id
instance (Ord a) => IsList (Set a) where
type Item (Set a) = a
fromList = Set.fromList
toList = Set.toList
Examples taken from OverloadedLists – GHC.
FunctionalDependencies
If you have a multi-parameter type-class with arguments a, b, c, and x, this extension lets you express that the type x can be uniquely identified from a, b, and c:
class SomeClass a b c x | a b c -> x where ...
When declaring an instance of such class, it will be checked against all other instances to make sure that the functional dependency holds, that is, no other instance with same a b c
but different x
exists.
You can specify multiple dependencies in a comma-separated list:
class OtherClass a b c d | a b -> c d, a d -> b where ...
For example in MTL we can see:
class MonadReader r m| m -> r where ...
instance MonadReader r ((->) r) where ...
Now, if you have a value of type MonadReader a ((->) Foo) => a
, the compiler can infer that a ~ Foo
, since the second argument completely determines the first, and will simplify the type accordingly.
The SomeClass
class can be thought of as a function of the arguments a b c
that results in x
. Such classes can be used to do computations in the typesystem.
GADTs
Conventional algebraic datatypes are parametric in their type variables. For example, if we define an ADT like
data Expr a = IntLit Int
| BoolLit Bool
| If (Expr Bool) (Expr a) (Expr a)
with the hope that this will statically rule out non-well-typed conditionals, this will not behave as expected since the type of IntLit :: Int -> Expr a
is universially quantified: for any choice of a
, it produces a value of type Expr a
. In particular, for a ~ Bool
, we have IntLit :: Int -> Expr Bool
, allowing us to construct something like If (IntLit 1) e1 e2
which is what the type of the If
constructor was trying to rule out.
Generalised Algebraic Data Types allows us to control the resulting type of a data constructor so that they are not merely parametric. We can rewrite our Expr
type as a GADT like this:
data Expr a where
IntLit :: Int -> Expr Int
BoolLit :: Bool -> Expr Bool
If :: Expr Bool -> Expr a -> Expr a -> Expr a
Here, the type of the constructor IntLit
is Int -> Expr Int
, and so IntLit 1 :: Expr Bool
will not typecheck.
Pattern matching on a GADT value causes refinement of the type of the term returned. For example, it is possible to write an evaluator for Expr a
like this:
crazyEval :: Expr a -> a
crazyEval (IntLit x) =
-- Here we can use `(+)` because x :: Int
x + 1
crazyEval (BoolLit b) =
-- Here we can use `not` because b :: Bool
not b
crazyEval (If b thn els) =
-- Because b :: Expr Bool, we can use `crazyEval b :: Bool`.
-- Also, because thn :: Expr a and els :: Expr a, we can pass either to
-- the recursive call to `crazyEval` and get an a back
crazyEval $ if crazyEval b then thn else els
Note that we are able to use (+)
in the above definitions because when e.g. IntLit x
is pattern matched, we also learn that a ~ Int
(and likewise for not
and if_then_else_
when a ~ Bool
).
ScopedTypeVariables
ScopedTypeVariables
let you refer to universally quantified types inside of a declaration. To be more explicit:
import Data.Monoid
foo :: forall a b c. (Monoid b, Monoid c) => (a, b, c) -> (b, c) -> (a, b, c)
foo (a, b, c) (b', c') = (a :: a, b'', c'')
where (b'', c'') = (b <> b', c <> c') :: (b, c)
The important thing is that we can use a
, b
and c
to instruct the compiler in subexpressions of the declaration (the tuple in the where
clause and the first a
in the final result). In practice, ScopedTypeVariables
assist in writing complex functions as a sum of parts, allowing the programmer to add type signatures to intermediate values that don’t have concrete types.
PatternSynonyms
Pattern synonyms are abstractions of patterns similar to how functions are abstractions of expressions.
For this example, let’s look at the interface Data.Sequence
exposes, and let’s see how it can be improved with pattern synonyms. The Seq
type is a data type that, internally, uses a complicated representation to achieve good asymptotic complexity for various operations, most notably both O(1) (un)consing and (un)snocing.
But this representation is unwieldy and some of its invariants cannot be expressed in Haskell’s type system. Because of this, the Seq
type is exposed to users as an abstract type, along with invariant-preserving accessor and constructor functions, among them:
empty :: Seq a
(<|) :: a -> Seq a -> Seq a
data ViewL a = EmptyL | a :< (Seq a)
viewl :: Seq a -> ViewL a
(|>) :: Seq a -> a -> Seq a
data ViewR a = EmptyR | (Seq a) :> a
viewr :: Seq a -> ViewR a
But using this interface can be a bit cumbersome:
uncons :: Seq a -> Maybe (a, Seq a)
uncons xs = case viewl xs of
x :< xs' -> Just (x, xs')
EmptyL -> Nothing
We can use view patterns to clean it up somewhat:
{-# LANGUAGE ViewPatterns #-}
uncons :: Seq a -> Maybe (a, Seq a)
uncons (viewl -> x :< xs) = Just (x, xs)
uncons _ = Nothing
Using the PatternSynonyms
language extension, we can give an even nicer interface by allowing pattern matching to pretend that we have a cons- or a snoc-list:
{-# LANGUAGE PatternSynonyms #-}
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
pattern Empty :: Seq a
pattern Empty <- (Seq.viewl -> Seq.EmptyL)
pattern (:<) :: a -> Seq a -> Seq a
pattern x :< xs <- (Seq.viewl -> x Seq.:< xs)
pattern (:>) :: Seq a -> a -> Seq a
pattern xs :> x <- (Seq.viewr -> xs Seq.:> x)
This allows us to write uncons
in a very natural style:
uncons :: Seq a -> Maybe (a, Seq a)
uncons (x :< xs) = Just (x, xs)
uncons _ = Nothing
RecordWildCards
See RecordWildCards