From 776e65149576cb7d8d20c12edc57e6b919c366ee Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Mon, 25 Jan 2016 13:26:29 +0000 Subject: Thermoprint 5 --- provider/posts/thermoprint-5.md | 70 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 provider/posts/thermoprint-5.md diff --git a/provider/posts/thermoprint-5.md b/provider/posts/thermoprint-5.md new file mode 100644 index 0000000..cb9db47 --- /dev/null +++ b/provider/posts/thermoprint-5.md @@ -0,0 +1,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 +[existintial 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 +~~~ -- cgit v1.2.3