blog-static/content/blog/haskell_newtype.md

95 lines
3.2 KiB
Markdown
Raw Permalink Normal View History

2023-01-29 21:08:31 -08:00
---
title: "Multiple Typeclass Instances using Newtype in Haskell"
date: 2022-04-28T23:09:42-07:00
expirydate: 2022-04-28T23:09:42-07:00
2023-01-29 21:08:31 -08: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