# roles

Composable class-based roles

http://github.com/matt-noonan/roles/

Version on this page: | 0.2.0.0 |

LTS Haskell 22.26: | 0.2.1.0 |

Stackage Nightly 2024-06-21: | 0.2.1.0 |

Latest on Hackage: | 0.2.1.0 |

**Edward A. Kmett, Matt Noonan**

**Matt Noonan**

`roles-0.2.0.0@sha256:4fa3f234da9e32fa3de9b9bec2cfcd9a2332d64a9ecc8d664fa6696489d75516,868`

#### Module documentation for 0.2.0.0

- Data

*(full list with versions)*:

# roles

Composable, class-based roles.

# Table of contents

# What is the cost of a `newtype`

?

The conventional wisdom is that Haskell’s `newtype`

gives you a zero-cost
abstraction–wrapping and unwrapping of `newtype`

s are purely a compile-time
operation. Unfortunately, this is not *quite* the case:

```
-- A zero-cost abstraction... or is it?
newtype User = User String
-- 'name x' and 'x' will refer to the same in-memory entity at run-time...
name :: User -> String
name (User x) = x
-- ...but 'maybeName x' and 'x' will not be the same at run-time:
-- a new value of type 'Maybe String' is allocated, despite being
-- identical to the input!
maybeName :: Maybe User -> Maybe String
maybeName = fmap name
```

See the POPL ’11 paper Generative Type Abstraction and Type-level Computation for a more
through investigation of the problem and a solution, and the ICFP ’14 paper Safe Zero-cost Coercions for Haskell for
implementation of the `Coercible`

typeclass in Haskell. Still more information can be found on the Haskell wiki.

# Background

## The magical `Coercible`

class

The solution described in the second paper was to introduce a typeclass `Coercible a b`

of the
form

```
class Coercible a b where
coerce :: a -> b
```

There is something a bit magical about a typeclass like this, that requires baked-in
compiler support:
Consider a declaration `newtype New = MkNew Old`

. Within the module where `New`

is
defined, we should be able to freely `coerce`

between `New`

and `Old`

. But then again,
if `MkNew`

is not exported, then *outside* of the module we should *not* be able to
`coerce`

between `New`

and `Old`

. As a result, the `Coercible`

class must involve special
compiler magic to ensure that `coerce`

is only available in the appropriate modules.

## Lifting coercions

Let’s revisit the `maybeName`

issue. Ideally, we would like to rewrite the example
to make the coercions explicit, to guarantee zero runtime cost:

```
newtype User = User String
name :: User -> String
name = coerce
-- GHC knows that it can coerce 'User' to 'String', but
-- how about 'Maybe User' to 'Maybe String'?
maybeName :: Maybe User -> Maybe String
maybeName = coerce
```

For this to work, we would need instances of `Coercible User String`

(provided by
GHC compiler magic, since we’re in the module where `User`

is defined) and
also `Coercible a b => Coercible (Maybe a) (Maybe b)`

.

You might expect GHC could implement a generic “coercion lifting” rule of the form
`Coercible a b => Coercible (f a) (f b)`

. Unfortunately this would be unsound
in the presence of type families:

```
newtype User = MkUser String
type family Fam
type instance Fam String = Int
type instance Fam User = Double
```

If GHC naively added the coercion lifting rule, then we would be able to
coerce from `Double`

to `Int`

by:

```
Coercible User String => Coercible (Fam User) (Fam String) -- a.k.a. Coercible Double Int!
```

This is obviously no good.

## Roles to the rescue

It seems that sometimes we can lift a `Coercion a b`

to a `Coercion (f a) (f b)`

(e.g. for `Maybe`

) and sometimes we cannot (e.g. for `Fam`

). To figure out when
a coercion `a -> b`

can be lifted to a coercion `f a -> f b`

, GHC infers a *role*
for the type parameter of `f`

. If `f`

can safely support coercion-lifting, then
we say `f`

’s type parameter has a *representational* role; otherwise, it has a
*nominal* role.

Happily, GHC will infer that `Maybe`

’s type parameter is representational, while
`Fam`

’s type parameter is nominal. This lets our definition `maybeName = coerce`

pass the compiler, while attempting to coerce an `Int`

to a `Double`

via
`Fam`

will fail.

# The `roles`

library

## What problem does this library solve?

Unfortunately, in GHC Haskell there is currently (circa late 2017) no way to write something like this:

```
coerceFirst :: (Coercible a b, Functor f) => [f a] -> Maybe (f b)
coerceFirst [] = Nothing
coerceFirst (x:_) = Just (coerce x)
{- GHC says:
• Couldn't match representation of type ‘f a’ with that of ‘f b’
arising from a use of ‘coerce’
NB: We cannot know what roles the parameters to ‘f’ have;
we must assume that the role is nominal
-}
```

GHC rightly refuses to lift the coercion from `a`

to `b`

into a coercion from
`f a`

to `f b`

: it does not have any assurance that the functor
`f`

uses its type parameter representationally.

In other words, this function needs to have a constraint `Representational f`

that
means something like “`f`

’s type parameter has a representational
role.”

This library simply provides the `Representational`

typeclass for a variety of
types in `base`

and `containers`

.

## How can I use this library?

Since it is not made up of GHC pixie-dust magic, `Representational`

needs a way to
convince GHC that the lifted coercion is allowed.
It does this via the lone function of the `Representational`

class:

```
class Representational f where
rep :: Coercion a b -> Coercion (f a) (f b)
```

A value of type `Coercion a b`

is like a certificate that tells GHC “you are allowed
to coerce `a`

to `b`

”. You cash the certificate in by using `coerceWith`

, yielding
an actual coercion from `a`

to `b`

. The `rep`

function simply converts a certificate
for coercing `a`

to `b`

into a certificate for coercing `f a`

into `f b`

.

We can now fix the example from the previous section:

```
coerceFirst :: (Coercible a b, Representational f, Functor f) => [f a] -> Maybe (f b)
coerceFirst [] = Nothing
coerceFirst (x:_) = Just (coerceWith (rep Coercion) x)
-- ~~~~~~~~~~~~~~~~~~~~~~~~~
-- |
-- This means: (1) Get a certificate verifying that we can coerce `a` to `b`.
-- This certificate is `Coercion`, and we got it by making use
-- of the constraint `Coercible a b`.
-- (2) Since `f` is `Representational`, we can use `rep` to upgrade
-- the certificate to a certificate for coercion from `f a` to `f b`.
-- (3) Use `coerceWith` to hand the certificate over to GHC, obtaining
-- an actual coercion from `f a` to `f b` in return.
{- GHC says: sounds good to me! -}
```

For another usage example, see the `withRecMap`

function from `justified-containers`

,
and the corresponding test case. A `Representational`

constraint is used to ensure
that large maps are not duplicated in memory, despite undergoing a complex series
of `newtype`

-related manipulations. An earlier version of `withRecMap`

worked by
`fmap`

ping newtype wrappers and unwrappers, which caused an accidental duplication
of the map.

## History

This package is a fork of Edward Kmett’s original `roles 0.1`

. It offers
fewer instances of `Representational`

, in exchange for a much smaller set
of dependencies. The instances that involve `new`

and `eta`

have been
removed, and instances for `containers`

have been added.

## Changes

## 0.2

- Drop lens, add containers dependency
- Remove instances that rely on ‘new’ and ‘eta’ (for now)

## 0.1

- Repository initialized