Haskell Language

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 shown, 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 as 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 Maps.

['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


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow