Haskell for madmen (8 Part Series)

*Post from my blog. If you're enjoying the series, please consider buying me a coffee. A lot is happening in my life so this one took a bit longer, my apologies.*

In this chapter we will handle input into the database, which will require a new typeclass. In order to truly understand what we're doing we'll first look at the mathematics of types.

# Algebraic data types

We've seen a few ways to declare types in Haskell, but have not declared their mathematical properties. There is an algebra for types just like there is one for numbers. The Unit type that we already know acts a lot like the number 1. There is also a 0, the Void, but that's mostly a type for theoretical use, since it has no inhabitants, and we couldn't possibly pass a value for a type that has no values. We also have some kind of addition and multiplication, plus one other operator.

Let's look at how this type algebra works using type constructs we've covered previously.

```
data MyType1 = MyType2 TypeA TypeB
```

denotes a type `MyType1`

that is inhabited by every combination of inhabitants from `TypeA`

and `TypeB`

.

We call this a *product* type, corresponding to the *Cartesian product* in set theory and the *product* in category theory. The number of its inhabitants is also equal (ignoring bottoms) to the product of the number of elements of its constituents, so pick your favorite memory aid!

We also call it a *conjunctive* type for its correspondence to a conjunction (logical `AND`

) in constructive logic.

We've also know the following type:

```
data MyType2
= MyType2A TypeA
| MyType2B TypeB
```

which denotes a type `MyType2`

that is inhabited by either a `TypeA`

or a `TypeB`

.

We call this a *sum* type, but also a *union* type in reference to Set theory, a *co-product* in reference to category theory (category theory likes to use "co-" to indicate that something is the dual of another thing), a *disjunctive* type in reference to disjunctions (logical `OR`

) or an *enum* type in reference to the fact that its constructors act as enumerators telling us how to interpret bits stored in memory.

As you can see, many fields in mathematics seem to be coming together here. That's because they are.

There's one more type operator that we've also encountered, the arrow for functions:

```
data MyType3 = MyType3 (TypeA -> TypeB)
```

No easy names from elementary mathematics for this one. It's an arrow, a *relation* in set theory, a *morphism* in category theory, an *implication* in constructive logic.

Products, sums and arrows are the basic type operators from which we can create every other type (well, mostly).

The following table might help you put type algebra into context, you absolutely do not need to understand every field, it *might* help you understand, if not, forget about it. Keep in mind that where numbers use equality, types use isomorphism (`≌`

) and logic uses provability (`⊢`

).

Types | Numbers | Logic | Sets |
---|---|---|---|

Void | 0 | False | ∅ |

Unit | 1 | True | {∅} |

a × b | a * b | a ∧ b | a × b |

a + b | a + b | a ∨ b | a ∪ b |

a → b | a → b | ||

a × Void ≌ Void | a * 0 = 0 | a ∧ False ⊢ False | a × ∅ = ∅ |

a + Void ≌ a | a + 0 = a | a ∨ False ⊢ a | a ∪ ∅ = a |

a × Unit ≌ a | a * 1 = a | a ∧ True ⊢ a | a × {∅} = {(x, ∅) | x ∈ a} ≌ a |

function application | modus ponens |

I've left relations in set theory out of this as they don't translate well symbolically from arrows.

# Simple type functions

We've used functors, applicatives, and monads on a few occasions. These are all classes of type functions. Now we're going to roll our own functor, and do something we wouldn't usually do, namely manually define its instance declarations rather than using `deriving`

. We do this to illustrate a problem.

```
data MyFunctor1 b = MyType1A TypeA | MyType1B b
instance Functor MyFunctor1 where
fmap someFunction myFunctor = case myFunctor of
MyType1A ta -> MyType1A ta
MyType1B someValue -> MyType1B $ someFunction someValue
```

There's a few things going on here.

First, we've declared a type which takes a type variable called `b`

. Remember that functors have kind `* -> *`

? We must therefore have some input type with kind `*`

. So `MyFunctor1 Int`

would construct the type `MyType1A TypeA | MyType1B Int`

. With the type algebra from above, we would write this type as:

MyFunctor1(b) = TypeA + b

Second, we've declared `MyFunctor1`

to be an instance of `Functor`

. `Functor`

requires the declaration of a function `fmap :: (a -> b) -> f a -> f b`

, in our case, `f`

is `MyFunctor1`

, so we need a:

```
fmap :: (a -> b) -> MyFunctor1 a -> MyFunctor1 b
```

Note that this `b`

is not necessarily the same `b`

as the type declaration, as they are in different scopes.

There is really only one way to turn an `f a`

into `f b`

if we're using sum and product types, which is to apply the function to every `a`

, because the only thing we know about `a`

is that it is input to `f`

, so there is nothing else we can do with it. This is also why we can simply `derive`

the functor instance for all our types, there at most 1 way to do it.

Things get a bit complicated with arrows:

```
-- This one is easy, we just use function composition
data MyFunctor2 b = MyFunctor2 (TypeA -> b)
instance Functor MyFunctor2 where
fmap someFunction (MyFunctor2 f) =
MyFunctor2 $ someFunction . f
-- This one is impossible
data MyFunctor3 a = MyFunctor3 (a -> TypeB)
instance Functor MyFunctor3 where
fmap someFunction (MyFunctor3 f) = ..?
```

We simply have no way to combine an `a -> b`

and `a -> c`

into a `c -> b`

.

What's happening here?

In the sum type `a + b`

, both arguments `a`

and `b`

are said to be in a positive position. Same for products `a ✕ b`

. In arrow types `a → b`

, `b`

is in a positive position, but `a`

is in a negative position. To "map" type variables in negative position, we need to flip the order of arrows:

```
invertedMap :: (c -> a) -> (a -> b) -> (c -> b)
invertedMap f a = a . f
```

If `a`

is in a negative position in the type `f a`

, we call `f`

a *contravariant* functor. Regular functors are *covariant*.

## Note:

Remember two ways in which Haskell is not total from last chapter? Contravariant type recursion is the third:

`data Silly = SillyConstructor (Silly -> a) doSilly :: Silly -> a doSilly silly@(SillyConstructor f) = f silly makeSilly :: Silly makeSilly = SillyConstructor doSilly anythingYouWant :: a anythingYouWant = doSilly makeSilly`

# Encoders

Hasql uses contravariant functors in its encoders. That makes sense, since encoders are also a sort of "input taker". It uses the module `Data.Functor.Contravariant`

, which requires `Contravariant`

s to have the function `contramap :: (a -> b) -> f b -> f a`

, which should look rather familiar with the knowledge above. Let's import the module:

```
import Data.Functor.Contravariant (contramap)
```

Before we dive into writing encoders, let's generalize our code a bit. Replace the old fetchFromDB:

```
connectionSettings :: Hasql.Connection.Settings
connectionSettings =
Hasql.Connection.settings
"localhost"
(fromInteger 5432)
"Haskell-student"
"Why-are-you-putting-credentials-in-code?-You-absolute-potato!"
"todolists"
runSessionAndClose :: Hasql.Session.Session a -> IO a
runSessionAndClose session =
do
connectionResult <- Hasql.Connection.acquire connectionSettings
case connectionResult of
Left (Just errMsg) -> error $ StrictUTF8.toString errMsg
Left Nothing -> error "Unspecified connection error"
Right connection -> do
sessionResult <- Hasql.Session.run session connection
Hasql.Connection.release connection
case sessionResult of
Right result -> return result
Left err -> error $ show err
fetchFromDB :: IO [Task]
fetchFromDB = runSessionAndClose selectTasksSession
```

Then, let's start with the statement, just as we did for decoders:

```
pushTaskStatement :: Hasql.Statement.Statement String ()
pushTaskStatement =
Hasql.Statement.Statement
"INSERT INTO todolist_ch5 ( \
\ task, \
\ done \
\)\
\VALUES \
\ ( $1, FALSE );"
taskNameEncoder
Hasql.Decoders.unit
True
taskNameEncoder :: Hasql.Encoders.Params String
taskNameEncoder = error "Encoder not implemented yet"
```

Hasql come swith a `Value Text`

decoder, but we need a `Value String`

! This time we will need a function `String -> Text`

rather than `Text -> String`

, because `Value`

is contravariant. We use the `pack`

from `Data.Text`

.

```
taskNameEncoder :: Hasql.Encoders.Params String
taskNameEncoder = Hasql.Encoders.param taskNameEncoder_value
taskNameEncoder_value :: Hasql.Encoders.Value String
taskNameEncoder_value = contramap Data.Text.pack Hasql.Encoders.text
```

Now we only need to put it all together in a session and run it:

```
pushTaskToDB :: String -> IO ()
pushTaskToDB taskDescription =
let
session :: Hasql.Session.Session ()
session = Hasql.Session.statement taskDescription pushTaskStatement
in
runSessionAndClose session
```

Don't forget to add `pushTaskToDB`

to the exports list.

Finally, let's call this function every time we start our server:

##### src/ServerMain.hs

```
startServer :: IO ()
startServer = do
TaskDB.pushTaskToDB "implement new feature request for client"
Warp.run 8080 requestHandler
```

You may have noticed this will create an ever-increasing list of tasks. That is correct, because feature requests from clients will never cease, which is why achieving world domination comes first.

Haskell for madmen (8 Part Series)

Posted on by:

## Discussion