Constraint Kinds

Tweet
Posted on January 13, 2017 by Kwang Yul Seo

In this blog post, I will show some examples of using the ConstraintKinds GHC extension.

Constraints

Constraints in Haskell mean one of the following things:

These constraints can only occur to the left of => arrow in standard Haskell.

Kinds

In Haskell, types have types which are called kinds.

Examples:

While data introduces types of kind *, classes introduce types of kind Constraint.

We can use tuple syntax to create empty constraints and combine constraints.

type NoConstraint = (() :: Constraint)
type Text a = (Read a, Show a)

ConstraintKinds

Classes and contexts were not first-class citizens in Haskell, but the introduction of the Constraint kind has changed this and allows them to be used as parameters of types.

{-# LANGUAGE ConstraintKinds #-}

With this extension, constraints can be used in new ways:

Let’s visit each use case with concrete examples.

Constraint synonyms

Since constraints are now just types, type synonyms can be reused on constraints.

type Text a = (Show a, Read a)

Here is a real world example of constraint synonyms defined in the Haskell Tool Stack.

-- | Constraint synonym for all of the common environment instances
type HasEnv r = (HasLogOptions r, HasTerminal r, HasReExec r, HasSticky r)

-- | Constraint synonym for constraints commonly satisifed by monads used in stack.
type StackM r m =
    (MonadReader r m, MonadIO m, MonadBaseControl IO m, MonadLoggerIO m, MonadMask m, HasEnv r)

Or we can define Func in terms of two type arguments ctx and a where ctx is a context of kind * -> Constraint.

type Func cxt a = cxt a => a -> a

inc :: Func Num a
inc = (+1)

Constraint families

Constraint families allow constraints to be indexed by a type in the same way that type families and data families allow types to be indexed by types.

For example, constraint families let us define a generalized version of the Monad class where we can impose some constraints on the element it can contain. Wolfgang Jeltsch’s The Constraint kind and Max Bolingbroke’s Constraint Kinds for GHC explain this classic example in details.

rmonad provides a collection of restricted monads based on associated datatypes.

class RMonad m where
  type RMonadCtxt m a :: Constraint
  type RMonadCtxt m a = ()

  return :: RMonadCtxt m a => a -> m a
  (>>=) :: (RMonadCtxt m a, RMonadCtxt m b) => m a -> (a -> m b) -> m b

instance RMonad S.Set where
  type RMonadCtxt S.Set a = Ord a
  return = S.singleton
  mx >>= fxmy = S.fromList [y | x <- S.toList mx, y <- S.toList (fxmy x)]      

instance RMonad [] where
  return x = [x]
  (>>=) = flip concatMap