summaryrefslogtreecommitdiff
path: root/provider/posts/thermoprint-5.md
blob: 47132e23e79676618dd229f3a9936f4eec3faff7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
---
title: Building an Extensible Framework for Specifying Compile-Time Configuration using Universal Quantification
tags: Thermoprint
published: 2016-01-24
---

When I write *Universal Quantification* I mean what is commonly referred to as
[existential quantification](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/data-type-extensions.html#existential-quantification),
which I think is a misnomer.  To wit:

$( \exists x \ldotp f(x) ) \to y$ is isomorphic to $\forall x \ldotp (f(x) \to y)$ (I
won´t try to back this claim up with actual category theory just now. You might want to
nag me occasionally if this bothers you -- I really should invest some more time into
category theory). Since haskell does not support `exists` we´re required to use the
`forall`-version, which really is universally quantified.

What we want is to have the user provide us with a set of specifications of how to
interact with one printer each.
Something like the following:

~~~ {.haskell}
newtype PrinterMethod = PM { unPM :: Printout -> IO (Maybe PrintingError) }

data Printer = Printer
  { print :: PrinterMethod
  , queue :: TVar Queue
  }
~~~

The first step in refining this is necessitated by having the user provide the
[monad-transformer-stack](http://book.realworldhaskell.org/read/monad-transformers.html)
to use at compile time.
Thus we introduce our first universal quantification (in conjunction with
[polymorphic components](https://prime.haskell.org/wiki/PolymorphicComponents)):

~~~ {.haskell}
newtype PrinterMethod = PM { unPm :: forall m. MonadResource m => Printout -> m (Maybe PrintingError) }
~~~

Since we don´t want to *burden* the user with the details of setting up `TVar Queue`{.haskell} we
also introduce function to help with that:

~~~ {.haskell}
printer :: MonadResource m => PrinterMethod -> m Printer
printer p = Printer p <$> liftIO (newTVarIO def)
~~~

We could at this point provide ways to set up `PrinterMethod`{.haskell}s and have the user
provide us with a list of them.

We, however, have numerous examples of printers which require some setup (such opening a
file descriptor). The idiomatic way to handle this is to decorate that setup with some
constraints and construct our list of printers in an
[`Applicative`{.haskell}](https://hackage.haskell.org/package/base/docs/Control-Applicative.html#t:Applicative)
fashion:

~~~ {.haskell}
printer :: MonadResource m => m PrinterMethod -> m Printer
printer p = Printer <$> p <*> liftIO (newTVarIO def)
~~~

At this point a toy implementation of a printer we might provide looks like this:

~~~ {.haskell}
debugPrint :: Applicative m => m PrinterMethod
debugPrint = pure . PM $ const return Nothing <=< liftIO . putStrLn . toString

toString :: Printout -> String
toString = undefined
~~~