On this page:
2.3.1 Defining typeclasses and typeclass instances
class
instance
derive-instance
2.3.2 Printing for debugging
Show
show
2.3.3 Equality
Eq
==
/  =
2.3.4 Semigroups and monoids
Semigroup
+  +
Monoid
mempty
2.3.5 Functors
Functor
map
<$>
<&>
<$
$>
ignore
2.3.6 Applicative functors
Applicative
pure
<*>
sequence
traverse
2.3.7 Monads
Monad
join
=<<
>>=
do
ap

2.3 Typeclasses

2.3.1 Defining typeclasses and typeclass instances

syntax

(class maybe-superclasses (class-id var-id ...)
  maybe-fundeps
  [method-id : method-type maybe-default-method-impl] ...
  maybe-deriving-transformer)
 
maybe-superclasses = superclass-constraint ... =>
  | 
     
maybe-fundeps = #:fundeps [fundep-spec ...]
  | 
     
fundep-spec = [determinant-id ...+ -> dependent-id ...+]
     
maybe-default-method-impl = default-method-impl-expr
  | 
     
maybe-deriving-transformer = #:deriving-transformer deriving-transformer-expr
  | 
Defines a typeclass. As the name implies, a typeclass describes a class, or set, of types. Types that belong to the class are known as instances or members of the class, which are defined using the associated instance form.

A typeclass defines a set of typeclass methods, each named method-id, which are operations that must be implemented by all members of the class. Implementations of typeclass methods must match the provided method-type, with the var-ids replaced by the types the instance is being defined for.

For each method-id, a default method may be provided as default-method-impl-expr, which will be used if any instance opts to not specify an explicit implementation for that method. Each default method implementation must be polymorphic enough to be a valid implementation for any instance of the class. Default methods are generally used when a typeclass method may be defined in terms of other typeclass methods, but the implementor can be given a choice of which methods to implement, or they can provide a more efficient implementation for commonly-used methods.

Each fundep-spec, if provided, declares a functional dependency between parameters of the class, where each determinant-id and dependent-id must be included in the class’s var-ids. A functional dependency introduces a constraint between instances of the class: each combination of the parameters specified by the determinant-ids must uniquely determine the parameters specified by the dependent-ids. For example, given the following class declaration:

(class (C a b c) #:fundeps [[a -> c]])

…then each instance of C with the same type for a must also have the same type for c. For example, these instances would be permitted together:

(instance (C Integer Unit String))
(instance (C Integer Bool String))

…but these instances would not:

(instance (C Integer Unit String))
(instance (C Integer Bool Double))

Restriction of this sort is useful mostly because the knowledge that a class will be used this way allows the typechecker to perform better type inference. Without a functional dependency, use of a typeclass-constrained function requires all of its parameters to be known, but with a functional dependency, the typechecker can solve a typeclass constraint when only a subset of the parameters are known.

If deriving-transformer-expr is provided, it is evaluated in the transformer environment to obtain a deriving transformer for the defined class. A deriving transformer must be a syntax transformer, which will be used to expand uses of derive-instance or data #:deriving clauses that reference the class.

syntax

(instance instance-spec
  [method-id method-expr] ...)
 
instance-spec = (class-id instance-type ...)
  | 
(forall [var-id ...] maybe-constraints
        (class-id instance-type ...))
     
maybe-constraints = instance-constraint ... =>
  | 
Defines a typeclass instance, which declares that the given instance-types belong to the typeclass bound to class-id.

syntax

(derive-instance class-id . args)

Derives a a typeclass instance using the deriving transformer of the typeclass bound to class-id. The deriving transformer procedure is applied to the syntax object that represents the entire derive-instance form. The result of applying the transformer should be an instance form.

2.3.2 Printing for debugging

typeclass

(class (Show a)

  [show : {a -> String}])
A class for converting values to String representations for the purposes of debugging. Generally, the Show instance for a type should produce a valid Hackett expression that, when evaluated, produces the value.

value

show : {a -> String}

Examples:
> (show 42)

: String

"42"

> (show "hello")

: String

"\"hello\""

> (show (Just 11))

: String

"(Just 11)"

> (show {1 :: 2 :: 3 :: Nil})

: String

"{1 :: 2 :: 3 :: Nil}"

2.3.3 Equality

typeclass

(class (Eq a)

  [== : {a -> a -> Bool}]
  [/= : {a -> a -> Bool}])
The class of types with a notion of equality. The == method should produce True if both of its arguments are equal, otherwise it should produce False, and /= should be its inverse.

An implementation for at least one of == or /= must be provided. If one is omitted, a default implementation will be provided in terms of the other.

value

== : {a -> a -> Bool}

Examples:
> {10 == 10}

: Bool

True

> {10 == 11}

: Bool

False

> {{1 :: 2 :: Nil} == {1 :: 2 :: Nil}}

: Bool

True

> {{1 :: 2 :: Nil} == {1 :: Nil}}

: Bool

False

> {{1 :: 2 :: Nil} == {1 :: 3 :: Nil}}

: Bool

False

value

/= : {a -> a -> Bool}

Examples:
> {10 /= 10}

: Bool

False

> {10 /= 11}

: Bool

True

> {{1 :: 2 :: Nil} /= {1 :: 2 :: Nil}}

: Bool

False

> {{1 :: 2 :: Nil} /= {1 :: Nil}}

: Bool

True

> {{1 :: 2 :: Nil} /= {1 :: 3 :: Nil}}

: Bool

True

2.3.4 Semigroups and monoids

typeclass

(class (Semigroup a)

  [++ : {a -> a -> a}])
The class of semigroups, types with an associative binary operation, ++. Generally, ++ defines some notion of combining or appending, as is the case with the instances for String and (List a), but this is not necessarily true.

value

++ : {a -> a -> a}

An associative operation. That is, ++ must obey the following law:

{{a ++ b} ++ c} = {a ++ {b ++ c}}

Examples:
> {"hello" ++ ", " ++ "world!"}

: String

"hello, world!"

> {{1 :: 2 :: Nil} ++ {3 :: 4 :: Nil}}

: (List Integer)

{1 :: 2 :: 3 :: 4 :: Nil}

typeclass

(class (Semigroup a) => (Monoid a)

  [mempty : a])
A monoid extends the notion of a semigroup with the notion of an identity element, mempty.

value

mempty : a

An identity element for ++. That is, mempty must obey the following laws:

{a ++ mempty} = a
{mempty ++ a} = a

Examples:
> (: mempty String)

: String

""

> (: mempty (List Integer))

: (List Integer)

Nil

2.3.5 Functors

typeclass

(class (Functor f)

  [map : (forall [a b] {{a -> b} -> (f a) -> (f b)})])
A class of types that are functors, essentially types that provide a mapping or “lifting” operation. The map function can be viewed in different ways:

All map implementations must obey the following laws:

(map id) = id
(map {f . g}) = {(map f) . (map g)}

value

map : (forall [a b] {{a -> b} -> (f a) -> (f b)})

Examples:
> (map (+ 1) {1 :: 2 :: Nil})

: (List Integer)

{2 :: 3 :: Nil}

> (map (+ 1) (Just 10))

: (Maybe Integer)

(Just 11)

> (map (+ 1) Nothing)

: (Maybe Integer)

Nothing

value

<$>

 : (forall [f a b] (Functor f) => {{a -> b} -> (f a) -> (f b)})
An alias for map, intended to be used in infix mode instead of prefix, especially when mixed with <*> in the same expression.

Examples:
> {(+ 1) <$> {1 :: 2 :: Nil}}

: (List Integer)

{2 :: 3 :: Nil}

> {(+ 1) <$> (Just 10)}

: (Maybe Integer)

(Just 11)

> {(+ 1) <$> Nothing}

: (Maybe Integer)

Nothing

value

<&>

 : (forall [f a b] (Functor f) => {(f a) -> {a -> b} -> (f b)})
A flipped version of <$> for when it’s preferable to take the function argument second, like & but lifted to a functor.

value

<$ : (forall [f a b] (Functor f) => {b -> (f a) -> (f b)})

Equivalent to {map . const}. Replaces all values of type a with a new value of type b.

Examples:
> {10 <$ (Just 1)}

: (Maybe Integer)

(Just 10)

> {10 <$ {1 :: 2 :: 3 :: Nil}}

: (List Integer)

{10 :: 10 :: 10 :: Nil}

value

$> : (forall [f a b] (Functor f) => {(f a) -> b -> (f b)})

A flipped version of <$.

value

ignore : (forall [f a] (Functor f) => {(f a) -> (f Unit)})

Replaces the result of a functor with Unit. Equivalent to (<$ Unit).

2.3.6 Applicative functors

typeclass

(class (Functor f) => (Applicative f)

  [pure : (forall [a] {a -> (f a)})]
  [<*> : (forall [a b] {(f {a -> b}) -> (f a) -> (f b)})])
The class of applicative functors, which are functors with some notion of application, <*>. Additionally, applicative functors must provided a lifting operation, pure, which embeds any value into f.

Applicative functors must satisfy the following laws:

{(pure id) <*> v} = v
{(pure .) <*> u <*> v <*> w} = {u <*> (v <*> w)}
{(pure f) <*> (pure x)} = (pure (f x))
{u <*> (pure y)} = {(pure (& y) <*> u)}

As a consequence of these laws, the Functor instance for f will satisfy:

(map f x) = {(pure f) <*> x}

value

pure : (forall [a] {a -> (f a)})

Lifts a value.

Examples:
> (: (pure 11) (Maybe Integer))

: (Maybe Integer)

(Just 11)

> (: (pure 11) (List Integer))

: (List Integer)

{11 :: Nil}

value

<*> : (forall [a b] {(f {a -> b}) -> (f a) -> (f b)})

Applies a function in a context. While map/<$> “lifts” a pure function to a function that operates on a functor, <*> applies a function that is already inside the context of a functor.

Examples:
> {(Just not) <*> (Just True)}

: (Maybe Bool)

(Just False)

> {(Just not) <*> (Just False)}

: (Maybe Bool)

(Just True)

> {(Just not) <*> Nothing}

: (Maybe Bool)

Nothing

> {(: Nothing (Maybe {Bool -> Bool})) <*> (Just True)}

: (Maybe Bool)

Nothing

Due to currying, this is especially useful in combination with <$> to apply a multi-argument function to multiple arguments within the context of some functor. For example, Maybe implements a sort of short-circuiting, where any Nothing will cause the entire computation to produce Nothing.

Examples:
> {+ <$> (Just 1) <*> (Just 2)}

: (Maybe Integer)

(Just 3)

> {+ <$> Nothing <*> (Just 2)}

: (Maybe Integer)

Nothing

> {+ <$> (Just 1) <*> Nothing}

: (Maybe Integer)

Nothing

This works because {f <$> x} is guaranteed to be equivalent to {(pure f) <*> x} by the applicative laws, and since functions are curried, each use of <*> applies a single argument to the (potentially partially-applied) function.

value

sequence

 : (forall [f a] (Applicative f) => {(List (f a)) -> (f (List a))})
Produces an action that runs a list of applicative actions from left to right, then collects the results into a new list.

Examples:
> (sequence {(Just 1) :: (Just 2) :: (Just 3) :: Nil})

: (Maybe (List Integer))

(Just {1 :: 2 :: 3 :: Nil})

> (sequence {(Just 1) :: Nothing :: (Just 3) :: Nil})

: (Maybe (List Integer))

Nothing

value

traverse

 : (forall [f a b] (Applicative f) => {{a -> (f b)} -> (List a) -> (f (List b))})
Applies a function to each element of a list to produce an applicative action, then collects them left to right a la sequence ((traverse f xs) is equivalent to (sequence (map f xs))).

Examples:
> (traverse head {{1 :: Nil} :: {2 :: 3 :: Nil} :: Nil})

: (Maybe (List Integer))

(Just {1 :: 2 :: Nil})

> (traverse head {{1 :: Nil} :: Nil :: Nil})

: (Maybe (List Integer))

Nothing

2.3.7 Monads

typeclass

(class (Applicative m) => (Monad m)

  [join : (forall [a] {(m (m a)) -> (m a)})]
  [=<< : (forall [a b] {{a -> (m b)} -> (m a) -> (m b)})])
The class of monads, which are applicative functors augmented with a single join operation that allows multiple “layers” of m to be “flattened” into a single one. Like functors and applicative functors, monads are a highly abstract concept, but they can be most usefully thought of as an abstraction notion of sequential actions.

Monads must satisfy the following laws:

{join . pure} = id
{join . (map pure)} = id
{join . join} = {join . (map join)}

The =<< operation is pronounced “bind”, and it is equivalent in power to join. While join is closer to the essence of what a monad is, =<< (or its flipped version, >>=) is more frequently used in ordinary programming. Either may be implemented, and a default implementation will be provided for the other, or an implementation may be provided for both if a more efficient implementation can be provided.

It is often more useful to use do than to use join or =<< directly.

value

join : (forall [a] {(m (m a)) -> (m a)})

Examples:
> (join (Just (Just 3)))

: (Maybe Integer)

(Just 3)

> (join (Just (: Nothing (Maybe Integer))))

: (Maybe Integer)

Nothing

> (join (: Nothing (Maybe (Maybe Integer))))

: (Maybe Integer)

Nothing

> (join {{1 :: Nil} :: {2 :: 3 :: Nil} :: Nil})

: (List Integer)

{1 :: 2 :: 3 :: Nil}

value

=<< : (forall [a b] {{a -> (m b)} -> (m a) -> (m b)})

Applies a function that produces a monadic value to a monadic value. The expression {f =<< x} is equivalent to (join {f <$> x}) (and an explicit implementation of both methods must maintain that law). It is worth comparing and contrasting the types of map/<$>, <*>, and =<<, all of which are similar but slightly different.

Examples:
> {head =<< (tail {1 :: 2 :: Nil})}

: (Maybe Integer)

(Just 2)

> {head =<< (tail {1 :: Nil})}

: (Maybe Integer)

Nothing

> {head =<< (tail (: Nil (List Integer)))}

: (Maybe Integer)

Nothing

value

>>=

 : (forall [m a b] (Monad m) => {(m a) -> {a -> (m b)} -> (m b)})
A flipped version of =<<.

syntax

(do do-clause ... monadic-expr)

 
do-clause = monadic-do-clause
  | pure-do-clause
     
monadic-do-clause = [binding-id <- monadic-expr]
  | monadic-expr
     
pure-do-clause = (let binding-pair ...)
  | (letrec binding-pair ...)
A convenient, imperative-style shorthand for a sequence of monadic expressions chained together with >>=. Each do-clause corresponds to a single use of >>=, and each monadic-expr must have a type with the shape (m a), where m is a Monad.

The do form is desugared using the following rules:

  1. Any use of do with a single subform expands to the subform—(do expr) is equivalent to expr.

  2. Each monadic-do-clause introduces a use of >>=, with the result potentially bound to a binding-id. That is, (do [x <- m] more ...+) expands to {ma >>= (λ [x] (do more ...))}, and (do m more ...+) expands to {ma >>= (λ [_] (do more ...))}.

  3. Each pure-do-clause produces a local binding form without any uses of >>=, which is useful to create local bindings that are not monadic. (do (let binding-pair ...) more ...+) expands to (let (binding-pair ...) (do more ...)), and (do (letrec binding-pair ...) more ...+) expands to (letrec (binding-pair ...) (do more ...)).

Using do is often much more readable than writing the uses of >>= out by hand, especially when it is helpful to give the result of each action a name.

Examples:
> (do [xs <- (tail {1 :: 2 :: 3 :: 4 :: Nil})]
      [ys <- (tail xs)]
      [zs <- (tail ys)]
      (head zs))

: (Maybe Integer)

(Just 4)

> (do [xs <- (tail {1 :: 2 :: 3 :: Nil})]
      [ys <- (tail xs)]
      [zs <- (tail ys)]
      (head zs))

: (Maybe Integer)

Nothing

> (do (let [x 1]
           [y 2])
      (println {"x is " ++ (show x)})
      (println {"y is " ++ (show y)})
      (let [z {x + y}])
      (println {"x + y is " ++ (show z)}))

x is 1

y is 2

x + y is 3

: Unit

Unit

value

ap

 : (forall [m a b] (Monad m) => {(m {a -> b}) -> (m a) -> (m b)})
An implementation of <*> in terms of map, pure, and join. This can be used as an implementation of <*> as long as join does not use <*> (if it does, the two will likely infinitely mutually recur).