95 lines
3.2 KiB
Markdown
95 lines
3.2 KiB
Markdown
---
|
|
title: "Multiple Typeclass Instances using Newtype in Haskell"
|
|
date: 2022-04-28T23:09:42-07:00
|
|
expirydate: 2022-04-28T23:09:42-07:00
|
|
tags: ["Haskell"]
|
|
draft: true
|
|
---
|
|
|
|
> And I have known the eyes already, known them all—
|
|
>
|
|
> The eyes that fix you in a formulated phrase . . .
|
|
|
|
It sometimes feels like the data types that we work with are simply
|
|
too locked down through their typeclass instances. A type might have multiple
|
|
law-abiding implementations of the required methods, and yet only one implementation can be used
|
|
in an `instance` declaration. First, let's take a look at some type classes and types
|
|
for which this is true, starting with `Monoid`.
|
|
|
|
### The `Monoid` Type Class
|
|
Recall that for a type `m` with `Monoid m`, the following things are available:
|
|
|
|
* An associative binary operation, `(<>)`. Associativity means that for any
|
|
three elements of `m`, like `a`, `b`, and `c`, the following
|
|
holds:
|
|
|
|
```Haskell
|
|
(a <> b) <> c == a <> (b <> c)
|
|
```
|
|
* An neutral element under `(<>)`, called `mempty`. It being the neutral element
|
|
means that using it with `(<>)` doesn't do anything. For any `a :: m`, we
|
|
can (with minor notation abuse) write the following:
|
|
|
|
```Haskell
|
|
a <> mempty == mempty <> a == a
|
|
```
|
|
|
|
This is a pretty broad specification, actually. Many types have multiple different
|
|
operations that satisfy the `Monoid` laws. Let's take a look at a few of them,
|
|
starting with integers.
|
|
|
|
#### Integers
|
|
Addition is an associative binary operation. Furthermore, it's well-known that adding zero to a
|
|
number leaves that number intact: \(0+n = n + 0 = n\). So we might define a `Monoid` instance for
|
|
numbers as follows. Note that we actually provide `(<>)` via the `Semigroup` class,
|
|
which _just_ requires the associative binary operation, and serves as a superclass for `Monoid`.
|
|
|
|
```Haskell
|
|
instance Semigroup Int where
|
|
(<>) = (+)
|
|
|
|
instance Monoid Int where
|
|
mempty = 0
|
|
```
|
|
|
|
Cool and good. But hey, there are other binary operations on integers! What about
|
|
multiplication? It is also associative, and again it is well-known that multiplying
|
|
anything by one leaves that number as it was: \(1\*n = n\*1 = n\). The corresponding
|
|
`Monoid` instance would be something like the following:
|
|
|
|
```Haskell
|
|
instance Semigroup Int where
|
|
(<>) = (*)
|
|
|
|
instance Monoid Int where
|
|
mempty = 1
|
|
```
|
|
|
|
But we can't have both. Haskell yells at us:
|
|
|
|
```
|
|
Duplicate instance declarations:
|
|
instance Semigroup Int -- Defined at test.hs:1:10
|
|
instance Semigroup Int -- Defined at test.hs:7:10
|
|
|
|
Duplicate instance declarations:
|
|
instance Monoid Int -- Defined at test.hs:4:10
|
|
instance Monoid Int -- Defined at test.hs:10:10
|
|
```
|
|
|
|
Okay, so we can have at most one. But that's not good.
|
|
Fortunately, thanks to the `Num` instance for `Int`, we get
|
|
functions that are pretty much the same as `fold`, except
|
|
specialized to multiplication and addition:
|
|
|
|
```Haskell
|
|
fold :: (Foldable t, Monoid m) => t m -> m
|
|
product :: (Foldable t, Num a) => t a -> a
|
|
sum :: (Foldable t, Num a) => t a -> a
|
|
```
|
|
|
|
This takes care of _most_ of the uses we have for `(+)` and `(*)`;
|
|
it does, however, prevent us from using `Int` with [`MonadWriter`](https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Writer-Lazy.html#t:MonadWriter).
|
|
|
|
#### Booleans
|