GHC Language Extensions

Andrew McMiddlin

2019-05-15

data61 logo


type-class-extensions.lhs:3:3: error:
    • Too many parameters for class ‘Foo’
      (Enable MultiParamTypeClasses to allow multi-parameter classes)
    • In the class declaration for ‘Foo’
  |
3 | > class Foo a b where
  |   ^^^^^^^^^^^^^^^^^^^...

Language extensions 101

Haskell 2010

Haskell 2010 is defined in the Haskell 2010 Language Report.

What’s not in Haskell 2010?

  • Type classes with more than one parameter.
  • String literals for anything other than [Char]
  • Generalised Algebraic Data Types (GADTs)

Language extensions

Section 12.3 covers the LANGUAGE pragma, which is used for extensions.

Enabling extensions in GHC

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GADTs, ScopedTypeVariables #-}

default-extensions:    OverloadedStrings
                     , GADTs
                     , ScopedTypeVariables

ghc -XOverloadedStrings Foo.hs

$ ghci
λ :set -XOverloadedStrings

Sugar

OverloadedStrings

Enable overloaded string literals.


GHCi, version 8.6.4: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/andrew/git/dot-files/.ghci
λ> :t "Lambda"
"Lambda" :: [Char]
λ> :set -XOverloadedStrings 
λ> :t "Jam"
"Jam" :: Data.String.IsString p => p


class IsString a where
  fromString :: String -> a
instance IsString Text where fromString = pack
isGood :: Text -> Bool isGood "foo"

TupleSections

Allow partially applied tuple constructors.


\x -> x * 2

(* 2)

\x -> (x,True)

(,True)


(,True,,,"hi",) :: a -> b -> c -> d -> (a,Bool,b,c,String,d)

InstanceSigs

Allow type signatures for definitions of instance members.


instance (Traversable f, Traversable g) => Traversable (Compose f g)
  traverse :: (a -> h b) -> Compose f g a -> h (Compose f g b)
  traverse = undefined


    • Illegal type signature in instance declaration:
        traverse' :: (a -> h b) -> Compose f g a -> h (Compose f g b)
      (Use InstanceSigs to allow this)
    • In the instance declaration for ‘Traversable' (Compose f g)’
   |
25 |   traverse' :: (a -> h b) -> Compose f g a -> h (Compose f g b)
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

LambdaCase

Adds syntactic sugar for pattern matching on a function’s argument.

pretty ::
  -> Expr
  -> Text
pretty e = case e of
  LitI n -> pack $ show n
  LitB True -> "true"
  LitB False -> "false"

pretty ::
  -> Expr
  -> Text
pretty = \case
  LitI n -> pack $ show n
  LitB True -> "true"
  LitB False -> "false"

MultiWayIf

Adds syntactic sugar for nested if-then-else expressions.

  if 1 < 0 then
    "foo"
  else if 12 > 4 then
    "bar"
  else if even 42 then
    "42"
  else
    "no idea"

  if | 1 < 0 -> "foo"
     | 12 > 4 -> "bar"
     | even 42 -> "42"
     | otherwise -> "no idea"

Records

RecordWildCards

Elide fields from record construction and pattern matching.


{-# LANGUAGE RecordWildCards #-}

data Person =
  Person {
    firstName :: Text
  , surname   :: Text
  , height    :: Integer
  }

greetPerson ::
  Person
  -> Text
greetPerson Person{firstName = firstName, surname = surname, height = height}Person{..} =
  undefined


{-# LANGUAGE RecordWildCards   #-}

defaultPerson ::
  Person
defaultPerson =
  let
    firstName = "Andrew"
    surname = "McMiddlin"
    height = 185
  in
    Person {..}


{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE RecordWildCards   #-}

data ConferenceAttendee =
  ConferenceAttendee {
    firstName :: Text
  , surname   :: Text
  , height    :: Integer
  , shirtSize :: ShirtSize
  }

defaultConferenceAttendee ::
  Person
  -> ConferenceAttendee
defaultConferenceAttendee Person{..} =
  ConferenceAttendee {shirtSize = M, ..}

Some problems with RecordWildCards

  • Unclear where variables come from.
  • All fields are brought into scope.
  • Vulnerable to changes in the record.

NamedFieldPuns

Remove some of the boilerplate when bringing record fields into scope.


{-# LANGUAGE NamedFieldPuns #-}

greetPerson ::
  Person
  -> Text
greetPerson Person{firstName, surname, height} =
  undefined


{-# LANGUAGE NamedFieldPuns #-}

greetPerson ::
  Person
  -> Text
greetPerson Person{firstName, surname} =
  undefined

Heavyweight

ScopedTypeVariables

Scope type variables to the lexical scope of the expression.


f ::
  [a] -> [a]
f xs =
  ys ++ ys
  where
    ys :: [a]
    ys = reverse xs


Couldn't match type ‘a’ with ‘a1’
‘a’ is a rigid type variable bound by
  the type signature for:
    f :: forall a. [a] -> [a]
  at examples/ScopedTypeVariables.hs:(5,1)-(6,12)
‘a1’ is a rigid type variable bound by
  the type signature for:
    ys :: forall a1. [a1]
  at examples/ScopedTypeVariables.hs:10:5-13
Expected type: [a1]
  Actual type: [a]


{-# LANGUAGE ScopedTypeVariables #-}

f ::
  forall a.
  [a] -> [a]
f xs =
  ys ++ ys
  where
    ys :: [a]
    ys = reverse xs

GeneralisedNewtypeDeriving

Derive instances for newtypes based on the type they wrap.


class Pretty a where
  pretty :: a -> Text

instance Pretty Int where
  pretty = pack . show

newtype Age = Age Int
  deriving (Show, Pretty)


Can't make a derived instance of ‘Pretty Age’:
  ‘Pretty’ is not a stock derivable class (Eq, Show, etc.)
  Try GeneralizedNewtypeDeriving for GHC's newtype-deriving extension


class Coercible a b

coerce :: Coercible a b => a -> b


{-# LANGUAGE GeneralisedNewtypeDeriving #-}

instance Pretty Int where
  pretty = pack . show

newtype Age = Age Int
  deriving (Show, Pretty)
  
instance Coercible Int Age
instance Coercible Age Int

instance Pretty Age where
  pretty = coerce $ pack . show
  
instance Coercible a b => Coercible (a -> c) (b -> c)

Roles

GeneralisedNewtypeDeriving as it was originally implemented had some issues that resulted in roles being added to the language.

As a result of the role system, adding join to the Monad class would stop GeneralisedNewtypeDeriving from being able to derive Monad.

Type Classes

Type classes in Haskell 2010

Section 4.3.1 of the standard covers type classes.

To summarise, it says that a type class declaration must have the following form.


class cx => C u where cdecls
  • must have the class keyword;
  • may have a context;
  • must have a class name;
  • must be parameterised over exactly one type; and
  • may declare one or more members.


class Show a where
  show :: a -> String
  ...

class Eq a => Ord a where
  compare :: a -> a -> Ordering
  ...

class (Ord a, Show a) => ShOrd a

Type class instances in Haskell 2010

Section 4.3.2 of the standard covers type class instance declarations.

In short, it says that a type class instance must have the following form.


instance cx => C (T u1 … uk) where { d }
  • must start with the instance keyword;
  • may have a context;
  • must mention the class name;
  • must mention the type the instance is for; and
  • may contain definitions for the class’s members.

MultiParamTypeClasses

Allows type classes with more than one type parameter.

class Monad m => MonadReader r m where
  ask :: m r
  ...

FlexibleInstances

Relaxes the rules for valid type class instances.

  • Instance types can be type variables.
  • Type variables can appear multiple times in the instance head.
  • Concrete types may be used as parameters to instance types.


class Monad m => MonadReader r m where
  ask :: m r

instance MonadReader r ((->) r) where
  ask = id


type-class-extensions.lhs:123:10-32: error:
    • Illegal instance declaration for ‘MonadReader r ((->) r)’
        (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.
         Use FlexibleInstances if you want to disable this.)
    • In the instance declaration for ‘MonadReader r ((->) r)’
    |
123 | instance MonadReader r ((->) r) where


class Twizzle a where
  twizzle :: a -> Int

instance Twizzle (Maybe Integer) where
  twizzle = maybe 42 fromInteger


$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.4.4

$ ghc -Wall -fforce-recomp Main.hs -o whoopsie
[1 of 4] Compiling FIA              ( FIA.hs, FIA.o )
[2 of 4] Compiling FIB              ( FIB.hs, FIB.o )
[3 of 4] Compiling FIC              ( FIC.hs, FIC.o )
[4 of 4] Compiling Main             ( Main.hs, Main.o )
Linking whoopsie ...

> ./whoopsie
fromList [Whoopsie A1 B C,Whoopsie A2 B C,Whoopsie A1 B C]

FlexibleContexts

Relax some of the requirements regarding contexts.


updateThing ::
  MonadState MyState m
  => m ()


updateThing ::
  ( HasThing s
  , MonadState s m
  )
  => m ()

FunctionalDependencies

Express dependent relationships between type variables for type classes with multiple parameters.


{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}

class Monad m => MonadReader r m where
  ask :: m r

instance MonadReader r ((->) r) where
  ask = id

foo ::
  Integer
foo =
  (+ 1) <$> ask $ 41


type-class-extensions.lhs:275:13-16: error:
    • Ambiguous type variable ‘t0’ arising from a use of ‘ask’
      prevents the constraint ‘(MonadReader
                                  Integer ((->) t0))’ from being solved.
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instance exist:
        one instance involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘(<$>)’, namely ‘ask’
      In the expression: (+ 1) <$> ask
      In the expression: (+ 1) <$> ask $ 100
    |
275 |   (+ 1) <$> ask $ 41
    |             ^^^


{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}

class Monad m => MonadReader r m | m -> r where
  ask :: m r

instance MonadReader r ((->) r) where
  ask = id

foo ::
  Integer
foo =
  (+ 1) <$> ask $ 41

Conclusion

  • Haskell 2010 is smaller than you think.
  • GHC defines many extensions to the language.
  • Language extensions come with tradeoffs.

References

Images