-
Notifications
You must be signed in to change notification settings - Fork 0
/
2021-03-19-custom-monad-with-servant-and-throwing-errors.html
137 lines (121 loc) · 11.1 KB
/
2021-03-19-custom-monad-with-servant-and-throwing-errors.html
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="alternate"
type="application/rss+xml"
href="https://magnus.therning.org/feed.xml"
title="RSS feed for https://magnus.therning.org/">
<title>Custom monad with servant and throwing errors</title>
<meta name="author" content="Magnus Therning"><meta name="referrer" content="no-referrer"><link href= "static/style.css" rel="stylesheet" type="text/css" /><link href= "static/htmlize.css" rel="stylesheet" type="text/css" /><link href= "static/extra_style.css" rel="stylesheet" type="text/css" /></head>
<body>
<div id="preamble" class="status"><div class="nav-bar"><a class="nav-link" href="./index.html">Top</a><a class="nav-link" href="./archive.html">Archive</a><a class="nav-link align-right" href="./feed.xml"><img src="static/rss-feed-icon.png" style="height: 24px;" /></a></div></div>
<div id="content">
<div class="post-date">19 Mar 2021</div><h1 class="post-title"><a href="https://magnus.therning.org/2021-03-19-custom-monad-with-servant-and-throwing-errors.html">Custom monad with servant and throwing errors</a></h1>
<p>
In the past I've always used <a href="https://hackage.haskell.org/package/scotty">scotty</a> when writing web services. This was mostly
due to laziness, I found working out how to use scotty a lot easier than
<a href="https://hackage.haskell.org/package/servant">servant</a>, so basically I was being lazy. Fairly quickly I bumped into some
limitations in scotty, but at first the workarounds didn't add too much
complexity and were acceptable. A few weeks ago they started weighing on me
though and I decided to look into servant and since I really liked what I found
I've started moving all projects to use servant.
</p>
<p>
In several of the projects I've used tagless final style and defined a type
based on <code>ReaderT</code> holding configuration over <code>IO</code>, that is something like
</p>
<div class="org-src-container">
<pre class="src src-haskell"><span class="org-haskell-keyword">newtype</span> <span class="org-haskell-type">AppM</span> a <span class="org-haskell-operator">=</span> <span class="org-haskell-constructor">AppM</span> <span class="org-rainbow-delimiters-depth-1">{</span>unAppM <span class="org-haskell-constructor">:</span> <span class="org-haskell-constructor">ReaderT</span> <span class="org-haskell-constructor">Config</span> <span class="org-haskell-constructor">IO</span> a<span class="org-rainbow-delimiters-depth-1">}</span>
<span class="org-haskell-keyword">deriving</span>
<span class="org-rainbow-delimiters-depth-1">(</span> <span class="org-haskell-constructor">Functor</span>,
<span class="org-haskell-constructor">Applicative</span>,
<span class="org-haskell-constructor">Monad</span>,
<span class="org-haskell-constructor">MonadIO</span>,
<span class="org-haskell-constructor">MonadReader</span> <span class="org-haskell-constructor">Config</span>
<span class="org-rainbow-delimiters-depth-1">)</span>
<span class="org-haskell-definition">runAppM</span> <span class="org-haskell-operator">::</span> <span class="org-haskell-type">AppM</span> a <span class="org-haskell-operator">-></span> <span class="org-haskell-type">Config</span> <span class="org-haskell-operator">-></span> <span class="org-haskell-type">IO</span> a
<span class="org-haskell-definition">runAppM</span> app <span class="org-haskell-operator">=</span> runReaderT <span class="org-rainbow-delimiters-depth-1">(</span>unAppM app<span class="org-rainbow-delimiters-depth-1">)</span>
</pre>
</div>
<p>
I found that servant is very well suited to this style through <a href="https://hoogle.haskell.org/?hoogle=hoistServer%20is%3Aexact&scope=set:stackage"><code>hoistServer</code></a> and
there are several examples on how to use it with a <code>ReaderT</code>-based type like
above. The first one I found is in the <a href="https://docs.servant.dev/en/stable/cookbook/using-custom-monad/UsingCustomMonad.html">servant cookbook</a>. However, as I realised
a bit later, using a simple type like this doesn't make it easy to trigger
responses with status other than <code>200 OK</code>. When I looked at the definition of
the type for writing handlers that ships with servant, <a href="https://hoogle.haskell.org/?hoogle=Handler%20package%3Aservant-server%20is%3Aexact&scope=set:stackage"><code>Handler</code></a>, I decided to
try to use the following type in my service
</p>
<div class="org-src-container">
<pre class="src src-haskell"><span class="org-haskell-keyword">newtype</span> <span class="org-haskell-type">AppM</span> a <span class="org-haskell-operator">=</span> <span class="org-haskell-constructor">AppM</span> <span class="org-rainbow-delimiters-depth-1">{</span>unAppM <span class="org-haskell-constructor">:</span> <span class="org-haskell-constructor">ReaderT</span> <span class="org-haskell-constructor">Config</span> <span class="org-rainbow-delimiters-depth-2">(</span><span class="org-haskell-constructor">ExceptT</span> <span class="org-haskell-constructor">ServerError</span> <span class="org-haskell-constructor">IO</span><span class="org-rainbow-delimiters-depth-2">)</span> a<span class="org-rainbow-delimiters-depth-1">}</span>
<span class="org-haskell-keyword">deriving</span>
<span class="org-rainbow-delimiters-depth-1">(</span> <span class="org-haskell-constructor">Functor</span>,
<span class="org-haskell-constructor">Applicative</span>,
<span class="org-haskell-constructor">Monad</span>,
<span class="org-haskell-constructor">MonadIO</span>,
<span class="org-haskell-constructor">MonadReader</span> <span class="org-haskell-constructor">Config</span>
<span class="org-rainbow-delimiters-depth-1">)</span>
<span class="org-haskell-definition">runAppM</span> <span class="org-haskell-operator">::</span> <span class="org-haskell-type">AppM</span> a <span class="org-haskell-operator">-></span> <span class="org-haskell-type">Config</span> <span class="org-haskell-operator">-></span> <span class="org-haskell-type">IO</span> <span class="org-rainbow-delimiters-depth-1">(</span><span class="org-haskell-type">Either</span> <span class="org-haskell-type">ServerError</span> a<span class="org-rainbow-delimiters-depth-1">)</span>
<span class="org-haskell-definition">runAppM</span> app <span class="org-haskell-operator">=</span> runExceptT <span class="org-haskell-operator">.</span> runReaderT <span class="org-rainbow-delimiters-depth-1">(</span>unAppM app<span class="org-rainbow-delimiters-depth-1">)</span>
</pre>
</div>
<p>
The natural transformation required by <code>hoistServer</code> can then be written like
</p>
<div class="org-src-container">
<pre class="src src-haskell"><span class="org-haskell-definition">nt</span> <span class="org-haskell-operator">::</span> <span class="org-haskell-type">AppM</span> a <span class="org-haskell-operator">-></span> <span class="org-haskell-type">Handler</span> a
<span class="org-haskell-definition">nt</span> x <span class="org-haskell-operator">=</span>
liftIO <span class="org-rainbow-delimiters-depth-1">(</span>runAppM x cfg<span class="org-rainbow-delimiters-depth-1">)</span> <span class="org-haskell-operator">>>=</span> <span class="org-haskell-operator">\</span><span class="org-haskell-keyword">case</span>
<span class="org-haskell-constructor">Right</span> v <span class="org-haskell-operator">-></span> pure v
<span class="org-haskell-constructor">Left</span> err <span class="org-haskell-operator">-></span> throwError err
</pre>
</div>
<p>
I particularly like how clearly this suggests a way to add custom errors if I
want that.
</p>
<ol class="org-ol">
<li>Swap out <code>ServerError</code> for my custom error type in <code>AppM</code>.</li>
<li>Write a function to transform my custom error type into a <code>ServerError</code>,
<code>transformCustomError :: CustomError -> ServerError</code>.</li>
<li>use <code>throwError $ transformCustomError err</code> in the <code>Left</code> branch of <code>nt</code>.</li>
</ol>
<div id="outline-container-org656a1f3" class="outline-2">
<h2 id="org656a1f3">A slight complication with <code>MonadUnliftIO</code></h2>
<div class="outline-text-2" id="text-org656a1f3">
<p>
I was using <a href="https://hackage.haskell.org/package/unliftio">unliftio</a> in my service, and as long as I based my monad stack only
on <code>ReaderT</code> that worked fine. I even got the <code>MonadUnliftIO</code> instance for free
through automatic deriving. <code>ExceptT</code> isn't a stateless monad though, so using
unliftio is out of the question, instead I had to switch to <a href="https://hoogle.haskell.org/?hoogle=MonadBaseControl%20package%3Amonad-control&scope=set:stackage"><code>MonadBaseControl</code></a>
and the packages that work with it. Defining and instance of <code>MonadBaseControl</code>
looked a bit daunting, but luckily <code>Handler</code> has an instance of it that I used
as inspiration.
</p>
<p>
First off <code>MonadBaseControl</code> requires the type to also be an instance of
<code>MonadBase</code>. There's an explicit implementation for <code>Handler</code>, but I found that
it can be derived automatically, so I took the lazy route.
</p>
<p>
The instance of <code>MonadBaseControl</code> for <code>AppM</code> ended up looking like this
</p>
<div class="org-src-container">
<pre class="src src-haskell"><span class="org-haskell-keyword">instance</span> <span class="org-haskell-type">MonadBaseControl</span> <span class="org-haskell-type">IO</span> <span class="org-haskell-type">AppM</span> <span class="org-haskell-keyword">where</span>
<span class="org-haskell-keyword">type</span> <span class="org-haskell-type">StM</span> <span class="org-haskell-type">AppM</span> a <span class="org-haskell-operator">=</span> <span class="org-haskell-type">Either</span> <span class="org-haskell-type">ServerError</span> a
liftBaseWith f <span class="org-haskell-operator">=</span> <span class="org-haskell-constructor">AppM</span> <span class="org-rainbow-delimiters-depth-1">(</span>liftBaseWith <span class="org-rainbow-delimiters-depth-2">(</span><span class="org-haskell-operator">\</span>g <span class="org-haskell-operator">-></span> f <span class="org-rainbow-delimiters-depth-3">(</span>g <span class="org-haskell-operator">.</span> unAppM<span class="org-rainbow-delimiters-depth-3">)</span><span class="org-rainbow-delimiters-depth-2">)</span><span class="org-rainbow-delimiters-depth-1">)</span>
restoreM <span class="org-haskell-operator">=</span> <span class="org-haskell-constructor">AppM</span> <span class="org-haskell-operator">.</span> restoreM
</pre>
</div>
<p>
I can't claim to really understand what's going on in that definition, but I
have Alexis King's article on <a href="https://lexi-lambda.github.io/blog/2019/09/07/demystifying-monadbasecontrol/">Demystifying MonadBaseControl</a> on my list of things
to read.
</p>
</div>
</div>
<div class="taglist"><a href="https://magnus.therning.org/tags.html">Tags</a>: <a href="https://magnus.therning.org/tag-haskell.html">haskell</a> <a href="https://magnus.therning.org/tag-servant.html">servant</a> </div></div>
<div id="postamble" class="status"><!-- org-static-blog-page-postamble --></div>
</body>
</html>