change citations to citep
[phd-thesis.git] / domain-specific_languages / first-class_datatypes.tex
1 \documentclass[../thesis.tex]{subfiles}
2
3 \begin{document}
4 \ifSubfilesClassLoaded{
5 \pagenumbering{arabic}
6 }{}
7
8 \chapter[First-class data types in shallow \acrshortpl{EDSL} using metaprogramming]{First-class data types in shallow \acrlongpl{EDSL} using metaprogramming}%
9 \label{chp:first-class_datatypes}%
10 \begin{chapterabstract}
11 Functional programming languages are excellent candidates for hosting \glspl{EDSL} because of their rich type systems, minimal syntax, and referential transparency.
12 However, data types defined in the host language are not automatically available in the embedded language.
13 To do so, all the operations on the data type must be ported to the \gls{EDSL} resulting in a lot of boilerplate.
14
15 This paper shows that by using metaprogramming, all first order user-defined data types can be automatically made first class in shallow \glspl{EDSL}.
16 We show this by providing an implementation in \gls{TH} for a typical \gls{DSL} with two different semantics.
17 Furthermore, we show that by utilising quasiquotation, there is hardly any burden on the syntax.
18 Finally, the paper also serves as a gentle introduction to \gls{TH}.
19 \end{chapterabstract}
20
21 \section{Introduction}
22 Functional programming languages are excellent candidates for hosting \glspl{EDSL} because of their rich type systems, minimal syntax, and referential transparency.
23 By expressing the language constructs in the host language, the parser, the type checker, and the run time can be inherited from the host language.
24 Unfortunately, data types defined in the host language are not automatically available in the \gls{EDSL}.
25
26 The two main strategies for embedding \glspl{DSL} in a functional language are deep embedding (also called initial) and shallow embedding (also called final).
27 Deep embedding represents the constructs in the language as data types and the semantics as functions over these data types.
28 This makes extending the language with new semantics effortless: just add another function.
29 In contrast, adding language constructs requires changing the data type and updating all existing semantics to support this new construct.
30 Shallow embedding on the other hand models the language constructs as functions with the semantics embedded.
31 Consequently, adding a construct is easy, i.e.\ it only entails adding another function.
32 Contrarily, adding semantics requires adapting all language constructs.
33 Lifting the functions to type classes, i.e.\ parametrising the constructs over the semantics, allows extension of the language both in constructs and in semantics orthogonally. This advanced style of embedding is called tagless-final or class-based shallow embedding~\citep{kiselyov_typed_2012}.
34
35 While it is often possible to lift values of a user-defined data type to a value in the \gls{DSL}, it is not possible to interact with it using \gls{DSL} constructs, they are not first-class citizens.
36
37 Concretely, it is not possible to
38 \begin{enumerate*}
39 \item construct values from expressions using a constructor,
40 \item deconstruct values into expressions using a deconstructor or pattern matching,
41 \item test which constructor the value holds.
42 \end{enumerate*}
43 The functions for this are simply not available automatically in the embedded language.
44 For some semantics---such as an interpreter---it is possible to directly lift the functions from the host language to the \gls{DSL}.
45 In other cases---e.g.\ \emph{compiling} \glspl{DSL} such as a compiler or a printer---this is not possible~\citep{elliott_compiling_2003}. %the torget this is not possible. cannot just be lifted from the host language to the \gls{DSL} so it requires a lot of boilerplate to define and implement them.
46 Thus, all of the operations on the data type have to be defined by hand requiring a lot of plumbing and resulting in a lot of boilerplate code.
47
48 To relieve the burden of adding all these functions, metaprogramming\nobreak---\nobreak\hskip0pt and custom quasiquoters---can be used.
49 Metaprogramming entails that some parts of the program are generated by a program itself, i.e.\ the program is data.
50 Quasiquotation is a metaprogramming mechanism that allows entering verbatim code for which a---possibly user defined---translation is used to convert the verbatim code to host language AST nodes.
51 Metaprogramming allows functions to be added to the program at compile time based on the structure of user-defined data types.
52
53 \subsection{Contributions of the paper}
54 This paper shows that with the use of metaprogramming, all first-order user-defined data types can automatically be made first class for shallow \glspl{EDSL}.
55 It does so by providing an implementation in \gls{TH} for a typical \gls{DSL} with two different semantics: an interpreter and a pretty printer.
56 Furthermore, we show that by utilising quasiquotation, there is hardly any burden on the syntax.
57 Finally, the paper also serves as a gentle introduction to \gls{TH} and reflects on the process of using \gls{TH}.
58
59 \section{Tagless-final embedding}
60 Tagless-final embedding is an upgrade to standard shallow embedding achieved by lifting all language construct functions to type classes.
61 As a result, views on the \gls{DSL} are data types implementing these classes.
62
63 To illustrate the technique, a simple \gls{DSL}, a language consisting of literals and addition, is outlined.
64 This language, implemented according to the tagless-final style~\citep{carette_finally_2009} in \gls{HASKELL}~\citep{peyton_jones_haskell_2003} consists initially only of one type class containing two functions.
65 The \haskellinline{lit} function lifts values from the host language to the \gls{DSL} domain.
66 The class constraint \haskellinline{Show} is enforced on the type variable \haskellinline{a} to make sure that the value can be printed.
67 The infix function \haskellinline{+.} represents the addition of two expressions in the \gls{DSL}.
68
69 \begin{lstHaskell}
70 class Expr v where
71 lit :: Show a => a -> v a
72 (+.) :: Num a => v a -> v a -> v a
73 infixl 6 +.
74 \end{lstHaskell}
75
76 The implementation of a view on the \gls{DSL} is achieved by implementing the type classes with the data type representing the view.
77 In the case of our example \gls{DSL}, an interpreter accounting for failure may be implemented as an instance for the \haskellinline{Maybe} type.
78 The standard infix functor application and infix sequential application are used so that potential failure is abstracted away from\footnotemark{}.
79 \begin{lrbox}{\LstBox}
80 \begin{lstHaskell}[frame=]
81 <$> :: (a -> b) -> f a -> f b
82 <*> :: f (a -> b) -> f a -> f b
83 infixl 4 <$>, <*>
84 \end{lstHaskell}
85 \end{lrbox}
86 \footnotetext{\usebox{\LstBox}}
87
88 \begin{lstHaskell}
89 instance Expr Maybe where
90 lit a = Just a
91 (+.) l r = (+) <$> l <*> r
92 \end{lstHaskell}
93
94 \subsection{Adding language constructs}
95 To add an extra language construct we define a new class housing it.
96 For example, to add division we define a new class as follows:
97
98 \begin{lstHaskell}
99 class Div v where
100 (/.) :: Integral a => v a -> v a -> v a
101 infixl 7 /.
102 \end{lstHaskell}
103
104 Division is an operation that undefined if the right operand is equal to zero.
105 To capture this behaviour, the \haskellinline{Nothing} constructor from \haskellinline{Maybe} is used to represent errors.
106 The right-hand side of the division operator is evaluated first.
107 If the right-hand side is zero, the division is not performed and an error is returned instead:
108
109 \begin{lstHaskell}
110 instance Div Maybe where
111 (/.) l r = l >>= \x->r >>= \y->
112 if y == 0 then Nothing else Just (x `div` y)
113 \end{lstHaskell}
114
115 \subsection{Adding semantics}
116 To add semantics to the \gls{DSL}, the existing classes are implemented with a novel data type representing the view on the \gls{DSL}.
117 First a data type representing the semantics is defined. In this case, the printer is kept very simple for brevity and just defined as a \haskellinline{newtype} of a string to store the printed representation\footnotemark{}.
118 \footnotetext{%
119 In this case a \haskellinline{newtype} is used instead of regular \haskellinline{data} declarations.
120 \haskellinline{newtype}s are special data types only consisting a single constructor with one field to which the type is isomorphic.
121 During compilation the constructor is completely removed resulting in no overhead~\cite[Sec.~4.2.3]{peyton_jones_haskell_2003}.
122 }
123 Since the language is typed, the printer data type has to have a type variable but it is only used during typing---i.e.\ a phantom type~\citep{leijen_domain_2000}:
124
125 \begin{lstHaskell}
126 newtype Printer a = P { runPrinter :: String }
127 \end{lstHaskell}
128
129 The class instances for \haskellinline{Expr} and \haskellinline{Div} for the pretty printer are straightforward and as follows:
130
131 \begin{lstHaskell}
132 instance Expr Printer where
133 lit a = P (show a)
134 (+.) l r = P ("(" ++ runPrinter l
135 ++ "+" ++ runPrinter r ++ ")")
136
137 instance Div Printer where
138 (/.) l r = P ("(" ++ runPrinter l
139 ++ "/" ++ runPrinter r ++ ")")
140 \end{lstHaskell}
141
142 \subsection{Functions}
143 Adding functions to the language is achieved by adding a multi-parameter class to the \gls{DSL}.
144 The type of the class function allows for the implementation to only allow first order function by supplying the arguments in a tuple.
145 Furthermore, by defining the \haskellinline{Main} type, the \gls{DSL} forces all functions to be defined at the top level and with the \haskellinline{:-} operator the syntax becomes usable.
146 Finally, by defining the functions as a \gls{HOAS} type safety is achieved~\citep{pfenning_higher-order_1988,chlipala_parametric_2008}.
147 The complete definition looks as follows:
148
149 \begin{lstHaskell}
150 class Function a v where
151 fun :: ( (a -> v s) -> In (a -> v s) (Main (v u)) ) -> Main (v u)
152 newtype Main a = Main { unmain :: a }
153 data In a b = a :- b
154 infix 1 :-
155 \end{lstHaskell}
156
157 Using the \haskellinline{Function} type class can be used to define functions with little syntactic overhead\footnote{The \GHCmod{LambdaCase} extension of GHC is used to reduce the number of brackets that allows lambda's to be an argument to a function without brackets or explicit function application using \haskellinline{\$}}.
158 The following listing shows an expression in the \gls{DSL} utilising two user-defined functions:
159
160 \begin{lstHaskell}
161 fun \increment-> (\x ->x +. lit 1)
162 :- fun \divide-> (\(x, y)->x /. y )
163 :- Main (increment (divide (lit 38, lit 5)))
164 \end{lstHaskell}
165
166 The interpreter only requires one instance of the \haskellinline{Function} class that works for any argument type.
167 In the implementation, the resulting function \haskellinline{g} is simultaneously provided to the definition \haskellinline{def}.
168 Because the laziness of \gls{HASKELL}'s lazy let bindings, this results in a fixed point calculation:
169
170 \begin{lstHaskell}
171 instance Function a Maybe where
172 fun def = Main (let g :- m = def g in unmain m)
173 \end{lstHaskell}
174
175 The given \haskellinline{Printer} type is not sufficient to implement the instances for the \haskellinline{Function} class, it must be possible to generate fresh function names.
176 After extending the \haskellinline{Printer} type to contain some sort of state to generate fresh function names and a \haskellinline{MonadWriter [String]}\footnotemark{} to streamline the output, we define an instance for every arity.
177 \begin{lrbox}{\LstBox}
178 \begin{lstHaskell}[frame=]
179 freshLabel :: Printer String
180 tell :: MonadWriter w m => w -> m ()
181 \end{lstHaskell}
182 \end{lrbox}
183 \footnotetext{\usebox{\LstBox}}
184 To illustrate this, the instance for unary functions is shown, all other arities are implemented in similar fashion.
185
186 \begin{lstHaskell}
187 instance Function () Printer where ...
188 instance Function (Printer a) Printer where ...
189 fun def = Main $ freshLabel >>= \f->
190 let g :- m = def $ \a0->const undefined
191 <$> (tell ["f", show f, " ("]
192 >> a0 >> tell [")"])
193 in tell ["let f", f, " a0 = "]
194 >> g (const undefined <$> tell ["a0"])
195 >> tell [" in "] >> unmain m
196 instance Function (Printer a, Printer b) Printer where ...
197 \end{lstHaskell}
198
199 Running the given printer on the example code shown before produces roughly the following output, running the interpreter on this code results in \haskellinline{Just 8}.
200
201 \begin{lstHaskell}
202 let f0 a1 = a1 + 1
203 in let f2 a3 a4 = a3 / a4
204 in f0 (f2 38 5)
205 \end{lstHaskell}
206
207 \subsection{Data types}
208 Lifting values from the host language to the \gls{DSL} is possible using the \haskellinline{lit} function as long as type of the value has instances for all the class constraints.
209 Unfortunately, once lifted, it is not possible to do anything with values of the user-defined data type other than passing them around.
210 It is not possible to construct new values from expressions in the \gls{DSL}, to deconstruct a value into the fields, nor to test of which constructor the value is.
211 Furthermore, while in the our language the only constraint is the automatically derivable \haskellinline{Show}, in real-world languages the class constraints may be very difficult to satisfy for complex types, for example serialisation to a single stack cell in the case of a compiler.
212
213 As a consequence, for user-defined data types---such as a pro\-gram\-mer-defined list type\footnotemark{}---to become first-class citizens in the \gls{DSL}, language constructs for constructors, deconstructors and constructor predicates must be defined.
214 Field selectors are also useful functions for working with user-defined data types, they are not considered for the sake of brevity but can be implemented using the deconstructor functions.
215 \footnotetext{
216 For example: \haskellinline{data List a = Nil \| Cons \{hd :: a, tl :: List a\}}
217 }
218 The constructs for the list type would result in the following class definition:
219
220 \begin{lstHaskell}
221 class ListDSL v where
222 -- constructors
223 nil :: v (List a)
224 cons :: v a -> v (List a) -> v (List a)
225 -- deconstructors
226 unNil :: v (List a) -> v b -> v b
227 unCons :: v (List a)
228 -> (v a -> v (List a) -> v b) -> v b
229 -- constructor predicates
230 isNil :: v (List a) -> v Bool
231 isCons :: v (List a) -> v Bool
232 \end{lstHaskell}
233
234 Furthermore, instances for the \gls{DSL}'s views need to be created.
235 For example, to use the interpreter, the following instance must be available.
236 Note that at first glance, it would feel natural to have \haskellinline{isNil} and \haskellinline{isCons} return \haskellinline{Nothing} since we are in the \haskellinline{Maybe} monad.
237 However, the this would fail the entire expression and the idea is that the constructor test can be done from within the \gls{DSL}.
238
239 \begin{lstHaskell}
240 instance ListDSL Maybe where
241 nil = Just Nil
242 cons hd tl = Cons <$> hd <*> tl
243 unNil d f = d >>= \Nil->f
244 unCons d f = d
245 >>= \(Cons hd tl)->f (Just hd) (Just tl)
246 isNil d = d >>= \case[+\footnotemark+]
247 Nil -> Just True
248 _ -> Just False
249 isCons d = d >>= \case
250 Cons _ _ -> Just True
251 Nil -> Just False
252 \end{lstHaskell}
253 \footnotetext{%
254 \haskellinline{\\case} is an abbreviation for \haskellinline{\\x->case x of ...} when using GHC's \GHCmod{LambdaCase} extension.
255 }
256
257 Adding these classes and their corresponding instances is tedious and results in boilerplate code.
258 We therefore resort to metaprogramming, and in particular \gls{TH}~\citep{sheard_template_2002} to alleviate this burden.
259
260 \section{Template metaprogramming}
261 Metaprogramming is a special flavour of programming where programs have the ability to treat and manipulate programs or program fragments as data.
262 There are several techniques to facilitate metaprogramming, moreover it has been around for many years now~\citep{lilis_survey_2019}.
263 Even though it has been around for many years, it is considered complex~\citep{sheard_accomplishments_2001}.
264
265 \gls{TH} is GHC's de facto metaprogramming system, implemented as a compiler extension together with a library~\citep{sheard_template_2002}\cite[Sec.~6.13.1]{ghc_team_ghc_2021}.
266 Readers already familiar with \gls{TH} can safely skip this section.
267
268 \gls{TH} adds four main concepts to the language, na\-me\-ly AST data types, splicing, quasiquotation and reification.
269 With this machinery, regular \gls{HASKELL} functions can be defined that are called at compile time, inserting generated code into the {AST}.
270 These functions are monadic functions operating in the \haskellinline{Q} monad.
271 The \haskellinline{Q} monad facilitates failure, reification and fresh identifier generation for hygienic macros~\citep{kohlbecker_hygienic_1986}.
272 Within the \haskellinline{Q} monad, capturable and non-capturable identifiers can be generated using the \haskellinline{mkName} and \haskellinline{newName} functions respectively.
273 The \emph{Peter Parker principle}\footnote{With great power comes great responsibility.} holds for the \haskellinline{Q} monad as well because it executes at compile time and is very powerful.
274 For example it can subvert module boundaries, thus accessing constructors that were hidden; access the structure of abstract types; and it may cause side effects during compilation because it is possible to call \haskellinline{IO} operations~\citep{terei_safe_2012}.
275 To achieve the goal of embedding data types in a \gls{DSL} we refrain from using these \emph{unsafe} features.
276
277 \subsubsection{Data types}
278 Firstly, for all of \gls{HASKELL}'s AST elements, data types are provided that are mostly isomorphic to the actual data types used in the compiler.
279 With these data types, the entire syntax of a \gls{HASKELL} program can be specified.
280 Often, a data type is suffixed with the context, e.g.\ there is a \haskellinline{VarE} and a \haskellinline{VarP} for a variable in an expression or in a pattern respectively.
281 To give an impression of these data types, a selection of data types available in \gls{TH} is given below:
282
283 \begin{lstHaskell}
284 data Dec = FunD Name [Clause] | DataD Cxt Name ...
285 | SigD Name Type | ClassD Cxt Name | ...
286 data Clause = Clause [Pat] Body [Dec]
287 data Pat = LitP Lit | VarP Name | TupP [Pat]
288 | WildP | ListP [Pat] | ...
289 data Body = GuardedB [(Guard, Exp)] | NormalB Exp
290 data Guard = NormalG Exp | PatG [Stmt]
291 data Exp = VarE Name | LitE Lit | AppE Exp Exp
292 | TupE [Maybe Exp] | LamE [Pat] Exp | ...
293 data Lit = CharL Char | StringL String
294 | IntegerL Integer | ...
295 \end{lstHaskell}
296
297 To ease creating AST data types in the \haskellinline{Q} monad, lowercase variants of the constructors are available that lift the constructor to the \haskellinline{Q} monad as.
298 For example, for the \haskellinline{LamE} constructor, the following \haskellinline{lamE} function is available.
299
300 \begin{lstHaskell}
301 lamE :: [Q Pat] -> Q Exp -> Q Exp
302 lamE ps es = LamE <$> sequence ps <*> es
303 \end{lstHaskell}
304
305 \subsubsection{Splicing}
306 Special splicing syntax (\haskellinline{\$(...)}) marks functions for compile-time execution.
307 Other than that they always produce a value of an AST data type, they are regular functions.
308 Depending on the context and location of the splice, the result type is either a list of declarations, a type, an expression or a pattern.
309 The result of this function, when successful, is then spliced into the code and treated as regular code by the compiler.
310 Consequently, the code that is generated may not be type safe, in which case the compiler provides a type error on the generated code.
311 The following listing shows an example of a \gls{TH} function generating on-the-fly functions for arbitrary field selection in a tuple.
312 When called as \haskellinline{\$(tsel 2 4)} it expands at compile time to \haskellinline{\\(_, _, f, _)->f}:
313
314 \begin{lstHaskell}
315 tsel :: Int -> Int -> Q Exp
316 tsel field total = do
317 f <- newName "f"
318 lamE [ tupP [if i == field then varP f else wildP
319 | i<-[0..total-1]]] (varE f)
320 \end{lstHaskell}
321
322 \subsubsection{Quasiquotation}
323 Another key concept of \gls{TH} is Quasiquotation, the dual of splicing~\citep{bawden_quasiquotation_1999}.
324 While it is possible to construct entire programs using the provided data types, it is a little cumbersome.
325 Using \emph{Oxford brackets} (\verb#[|# \ldots\verb#|]#) or single or double apostrophes, verbatim \gls{HASKELL} code can be entered that is converted automatically to the corresponding AST nodes easing the creation of language constructs.
326 Depending on the context, different quasiquotes are used:
327 \begin{itemize*}
328 \item \haskellinline{[\|...\|]} or \haskellinline{[e\|...\|]} for expressions
329 \item \haskellinline{[d\|...\|]} for declarations
330 \item \haskellinline{[p\|...\|]} for patterns
331 \item \haskellinline{[t\|...\|]} for types
332 \item \haskellinline{'...} for function names
333 \item \haskellinline{''...} for type names
334 \end{itemize*}.
335 It is possible to escape the quasiquotes again by splicing.
336 Variables defined within quasiquotes are always fresh---as if defined with \haskellinline{newName}---but it is possible to capture identifiers using \haskellinline{mkName}.
337 For example, \haskellinline{[|\\x->x|]} translates to \haskellinline{newName "x" >>= \\x->lamE [varP x] (varE x)} and does not interfere with other \haskellinline{x}s already defined.
338
339 \subsubsection{Reification}
340 Reification is the act of querying the compiler for information about a certain name.
341 For example, reifying a type name results in information about the type and the corresponding AST nodes of the type's definition.
342 This information can then be used to generate code according to the structure of data types.
343 Reification is done using the \haskellinline{reify :: Name -> Q Info} function.
344 The \haskellinline{Info} type is an \gls{ADT} containing all the---known to the compiler---information about the matching type: constructors, instances, etc.
345
346 \section{Metaprogramming for generating \gls{DSL} functions}
347 With the power of metaprogramming, we can generate the boilerplate code for our user-defined data types automatically at compile time.
348 To generate the code required for the \gls{DSL}, we define the \haskellinline{genDSL} function.
349 The type belonging to the name passed as an argument to this function is made available for the \gls{DSL} by generating the \haskellinline{typeDSL} class and view instances.
350 For the \haskellinline{List} type it is called as: \haskellinline{\$(genDSL ''List)}\footnotemark{}.
351 \footnotetext{
352 \haskellinline{''} is used instead of \haskellinline{'} to instruct the compiler to look up the information for \haskellinline{List} as a type and not as a constructor.
353 }
354
355 The \haskellinline{genDSL} function is a regular function---though \gls{TH} requires that it is defined in a separate module---that has type: \haskellinline{Name -> Q [Dec]}, i.e.\ given a name, it produces a list of declarations in the \haskellinline{Q} monad.
356 The \haskellinline{genDSL} function first reifies the name to retrieve the structural information.
357 If the name matches a type constructor containing a data type declaration, the structure of the type---the type variables, the type name and information about the constructors\footnotemark{}---are passed to the \haskellinline{genDSL'} function.
358 \footnotetext{
359 Defined as \haskellinline{type VarBangType = (Name, Bang, Type)} by \gls{TH}.
360 }
361 The \haskellinline{getConsName} function filters out unsupported data types such as \glspl{GADT} and makes sure that every field has a name.
362 For regular \glspl{ADT}, the \haskellinline{adtFieldName} function is used to generate a name for the constructor based on the indices of the fields\footnotemark{}.
363 \footnotetext{
364 \haskellinline{adtFieldName :: Name -> Integer -> Name}
365 }
366 From this structure of the type, \haskellinline{genDSL'} generates a list of declarations containing a class definition (\cref{sec_fcd:class}), instances for the interpreter (\cref{sec_fcd:interpreter}), and instances of the printer (\cref{sec_fcd:prettyprinter}) respectively.
367
368 \begin{lstHaskell}
369 genDSL :: Name -> Q [Dec]
370 genDSL name = reify name >>= \case
371 TyConI (DataD cxt typeName tvs mkind
372 constructors derives)
373 -> mapM getConsName constructors
374 >>= \d->genDSL' tvs typeName d
375 t -> fail ("genDSL does not support: " ++ show t)
376
377 getConsName :: Con -> Q (Name, [VarBangType])
378 getConsName (NormalC consName fs) = pure (consName,
379 [(adtFieldName consName i, b, t)
380 | (i, (b, t))<-[0..] `zip` fs])
381 getConsName (RecC consName fs) = pure (consName, fs)
382 getConsName c
383 = fail ("genDSL does not support: " ++ show c)
384
385 genDSL' :: [TyVarBndr] -> Name -> [(Name, [VarBangType])]
386 -> Q [Dec]
387 genDSL' typeVars typeName constructors = sequence
388 [ mkClass, mkInterpreter, mkPrinter, ... ]
389 where
390 (consNames, fields) = unzip constructors
391 ...
392 \end{lstHaskell}
393
394 \subsection{Class generation}\label{sec_fcd:class}
395 The function for generating the class definition is defined in the \haskellinline{where} clause of the \haskellinline{genDSL'} function.
396 Using the \haskellinline{classD} constructor, a single type class is created with a single type variable \haskellinline{v}.
397 The \haskellinline{classD} function takes five arguments:
398 \begin{enumerate*}
399 \item a context, i.e.\ the class constraints, which is empty in this case
400 \item a name, generated from the type name using the \haskellinline{className} function that simply appends the text \haskellinline{DSL}
401 \item a list of type variables, in this case the only type variable is the view on the \gls{DSL}, i.e.\ \haskellinline{v}
402 \item functional dependencies, empty in our case
403 \item a list of function declarations, i.e.\ the class members, in this case it is a concatenation of the constructors, deconstructors, and constructor predicates
404 \end{enumerate*}
405 Depending on the required information, either \haskellinline{zipWith} or \haskellinline{map} is used to apply the generation function to all constructors.
406
407 \begin{lstHaskell}
408 mkClass :: Q Dec
409 mkClass = classD (cxt []) (className typeName) [PlainTV (mkName "v")] []
410 ( zipWith mkConstructor consNames fields
411 ++ zipWith mkDeconstructor consNames fields
412 ++ map mkPredicate consNames
413 )
414 \end{lstHaskell}
415
416 In all class members, the view \haskellinline{v} plays a crucial role.
417 Therefore, a definition for \haskellinline{v} is accessible for all generation functions.
418 Furthermore, the \haskellinline{res} type represents the \emph{result} type, it is defined as the type including all type variables.
419 This result type is derived from the type name and the list of type variables.
420 In case of the \haskellinline{List} type, \haskellinline{res} is defined as \haskellinline{v (List a)} and is available for as well:
421
422 \begin{lstHaskell}
423 v = varT (mkName "v")
424 res = v `appT` foldl appT (conT typeName)
425 (map getName typeVars)
426 where getName (PlainTV name) = varT name
427 getName (KindedTV name _) = varT name
428 \end{lstHaskell}
429
430 \subsubsection{Constructors}
431 The constructor definitions are generated from just the constructor names and the field information.
432 All class members are defined using the \haskellinline{sigD} constructor that represents a function signature.
433 The first argument is the name of the constructor function, a lowercase variant of the actual constructor name generated using the \haskellinline{constructorName} function.
434 The second argument is the type of the function.
435 A constructor $C_k$ of type $T$ where
436 $T~tv_0~\ldots~tv_n = \ldots |~ C_k~a_0~\ldots~a_m~| \ldots~$
437 is defined as a \gls{DSL} function
438 $c_k \dcolon v~a_0 \shortrightarrow \ldots \shortrightarrow v~a_m \shortrightarrow v~(T~v_0~\ldots~v_n) $.
439 In the implementation, first the view \haskellinline{v} is applied to all the field types.
440 Then, the constructor type is constructed by folding over the lifted field types with the result type as the initial value using \haskellinline{mkCFun}.
441
442 \begin{lstHaskell}
443 mkConstructor :: Name -> [VarBangType] -> Q Dec
444 mkConstructor n fs
445 = sigD (constructorName n) (mkCFun fs res)
446
447 mkCFun :: [VarBangType] -> Q Type -> Q Type
448 mkCFun fs res = foldr (\x y->[t|$x -> $y|])
449 (map (\(_, _, t)->v `appT` pure t) fs)
450 \end{lstHaskell}
451
452 \subsubsection{Deconstructors}
453 The deconstructor is generated similarly to the constructor as the function for generating the constructor is the second argument modulo change in the result type.
454 A deconstructor $C_k$ of type $T$ is defined as a \gls{DSL} function
455 $\mathit{unC_k} \dcolon v~(T~v_0 \ldots v_n) \shortrightarrow (v~a_0 \shortrightarrow \ldots \shortrightarrow v~a_m \shortrightarrow v~b) \shortrightarrow v~b $.
456 In the implementation, \haskellinline{mkCFun} is reused to construct the type of the deconstructor as follows:
457
458 \begin{lstHaskell}
459 mkDeconstructor :: Name -> [VarBangType] -> Q Dec
460 mkDeconstructor n fs = sigD (deconstructorName n)
461 [t|$res -> $(mkCFun fs [t|$v $b|]) -> $v $b|]
462 where b = varT (mkName "b")
463 \end{lstHaskell}
464
465 \subsubsection{Constructor predicates}
466 The last part of the class definition are the constructor predicates, a function that checks whether the provided value of type $T$ contains a value with constructor $C_k$.
467 A constructor predicate for constructor $C_k$ of type $T$ is defined as a \gls{DSL} function $\mathit{isC_k} \dcolon v~(T~v_0~\ldots~v_n) \shortrightarrow v~\mathit{Bool}$.
468 A constructor predicate---name prefixed by \haskellinline{is}---is generated for all constructors.
469 They all have the same type:
470
471 \begin{lstHaskell}
472 mkPredicate :: Name -> Q Dec
473 mkPredicate n = sigD (predicateName n)
474 [t|$res -> $v Bool|]
475 \end{lstHaskell}
476
477 \subsection{Interpreter instance generation}\label{sec_fcd:interpreter}
478 Generating the interpreter for the \gls{DSL} means generating the class instance for the \haskellinline{Interpreter} data type using the \haskellinline{instanceD} function.
479 The first argument of the instance is the context, this is left empty.
480 The second argument of the instance is the type, the \haskellinline{Interpreter} data type applied to the class name.
481 Finally, the class function instances are generated using the information derived from the structure of the type.
482 The structure for generating the function instances is very similar to the class definition, only for the function instances of the constructor predicates, the field information is required as well as the constructor names.
483
484 \begin{lstHaskell}
485 mkInterpreter :: Q Dec
486 mkInterpreter = instanceD (cxt [])
487 [t|$(conT (className typeName)) Interpreter|]
488 ( zipWith mkConstructor consNames fields
489 ++ zipWith mkDeconstructor consNames fields
490 ++ zipWith mkPredicate consNames fields)
491 where ...
492 \end{lstHaskell}
493
494 \subsubsection{Constructors}
495 The interpreter is a view on the \gls{DSL} that immediately executes all operations in the \haskellinline{Maybe} monad.
496 Therefore, the constructor function can be implemented by lifting the actual constructor to the \haskellinline{Maybe} type using sequential application.
497 I.e.\ for a constructor $C_k$ this results in the following constructor: \haskellinline{ck a0 ... am = pure Ck <*> a0 <*> ... <*> am}.
498 To avoid accidental shadowing, fresh names for all the arguments are generated.
499 The \haskellinline{ifx} function is used as a shorthand for defining infix expressions\footnotemark{}
500 \begin{lrbox}{\LstBox}
501 \begin{lstHaskell}[frame=]
502 ifx :: String -> Q Exp -> Q Exp -> Q Exp
503 ifx op a b = infixE (Just a) (varE (mkName op)) (Just b)
504 \end{lstHaskell}
505 \end{lrbox}
506 \footnotetext{\usebox{\LstBox}}
507
508 \begin{lstHaskell}
509 mkConstructor :: Name -> [VarBangType] -> Q Dec
510 mkConstructor consName fs = do
511 fresh <- sequence [newName "a" | _<-fs]
512 fun (constructorName consName) (map varP fresh)
513 (foldl (ifx "<*>") [|pure $(conE consName)|]
514 (map varE fresh))
515 \end{lstHaskell}
516
517
518 \subsubsection{Deconstructors}
519 In the case of a deconstructor a function with two arguments is created: the object itself (\haskellinline{f}) and the function doing something with the individual fields (\haskellinline{d}).
520 To avoid accidental shadowing first fresh names for the arguments and fields are generated.
521 Then, a function is created with the two arguments.
522 First \haskellinline{d} is evaluated and bound to a host language function that deconstructs the constructor and passes the fields to \haskellinline{f}.
523 I.e.\ a deconstructor function $C_k$ is defined as: \haskellinline{unCk d f = d >>= \\(Ck a0 .. am)->f (pure a0) ... (pure am))}\footnotemark{}.
524 \footnotetext{
525 The \haskellinline{nameBase :: Name -> String} function from the \gls{TH} library is used to convert a name to a string.
526 }
527
528 \begin{lstHaskell}
529 mkDeconstructor :: Name -> [VarBangType] -> Q Dec
530 mkDeconstructor consName fs = do
531 d <- newName "d"
532 f <- newName "f"
533 fresh <- mapM (newName . nameBase . fst3) fs
534 fun (deconstructorName consName) [varP d, varP f]
535 [|$(varE d) >>= \($(match f))->$(fapp f fresh)|]
536 where fapp f = foldl appE (varE f)
537 . map (\f->[|pure $(varE f)|])
538 match f = pure (ConP consName (map VarP f))
539 \end{lstHaskell}
540
541 \subsubsection{Constructor predicates}
542 Constructor predicates evaluate the argument and make a case distinction on the result to determine the constructor.
543 To be able to generate a valid pattern in the case distinction, the total number of fields must be known.
544 To avoid having to explicitly generate a fresh name for the first argument, a lambda function is used.
545 In general, the constructor selector for $C_k$ results in the following code \haskellinline{isCk f = f >>= \\case Ck _ ... _ -> pure True; _ -> pure False}.
546 Generating this code is done with the following function:
547
548 \begin{lstHaskell}
549 mkPredicate :: Name -> [(Var, Bang, Type)] -> Q Dec
550 mkPredicate n fs = fun (predicateName n) []
551 [|\x->x >>= \case
552 $(conP n [wildP | _<-fs]) -> pure True
553 _ -> pure False|]
554 \end{lstHaskell}
555
556 \subsection{Pretty printer instance generation}\label{sec_fcd:prettyprinter}
557 Generating the printer happen analogously to the interpreter, a class instance for the \haskellinline{Printer} data type using the \haskellinline{instanceD} function.
558
559 \begin{lstHaskell}
560 mkPrinter :: Q Dec
561 mkPrinter = instanceD (cxt []) [t|$(conT (className typeName)) Printer|]
562 ( zipWith mkConstructor consNames fields
563 ++ zipWith mkDeconstructor consNames fields
564 ++ map mkPredicate consNames)
565 \end{lstHaskell}
566
567 To be able to define a printer that is somewhat more powerful, we provide instances for \haskellinline{MonadWriter}; add a state for fresh variables and a context; and define some helper functions the \haskellinline{Printer} datatype.
568 The \haskellinline{printLit} function is a variant of \haskellinline{MonadWriter}s \haskellinline{tell} that prints a literal string but it can be of any type (it is a phantom type anyway).
569 \haskellinline{printCons} prints a constructor name followed by an expression, it inserts parenthesis only when required depending on the state.
570 \haskellinline{paren} always prints parenthesis around the given printer.
571 \haskellinline{>->} is a variant of the sequence operator \haskellinline{>>} from the \haskellinline{Monad} class, it prints whitespace in between the arguments.
572
573 \begin{lstHaskell}
574 printLit :: String -> Printer a
575 printCons :: String -> Printer a -> Printer a
576 paren :: Printer a -> Printer a
577 (>->) :: Printer a1 -> Printer a2 -> Printer a3
578 pl :: String -> Q Exp
579 \end{lstHaskell}
580
581 \subsubsection{Constructors}
582 For a constructor $C_k$ the printer is defined as: \haskellinline{ck a0 ... am = printCons "Ck" (printLit "" >-> a0 >-> ... >-> am)}.
583 To generate the second argument to the \haskellinline{printCons} function, a fold is used with \haskellinline{printLit ""} as the initial element to account for constructors without any fields as well, e.g.\ \haskellinline{Nil} is translated to \haskellinline{nil = printCons "Nil" (printLit "")}.
584
585 \begin{lstHaskell}
586 mkConstructor :: Name -> [VarBangType] -> Q Dec
587 mkConstructor consName fs = do
588 fresh <- sequence [newName "f" | _<- fs]
589 fun (constructorName consName) (map varP fresh)
590 (pcons `appE` pargs fresh)
591 where pcons = [|printCons $(lift (nameBase consName))|]
592 pargs fresh = foldl (ifx ">->") (pl "")
593 (map varE fresh)
594 \end{lstHaskell}
595
596 \subsubsection{Deconstructors}
597 Printing the deconstructor for $C_k$ is defined as:
598 \begin{lstHaskell}
599 unCk d f
600 = printLit "unCk d"
601 >-> paren (
602 printLit "\(Ck" >-> printLit "a0 ... am" >> printLit ")->"
603 >> f (printLit "a0") ... (printLit "am")
604 )
605 \end{lstHaskell}
606
607 The implementation for this is a little elaborate and it heavily uses the \haskellinline{pl} function, a helper function that translates a string literal \haskellinline{s} to \haskellinline{[|printLit \$(lift s)|]}, i.e.\ it lifts the \haskellinline{printLit} function to the \gls{TH} domain.
608
609 \begin{lstHaskell}
610 mkDeconstructor :: Name -> [VarBangType] -> Q Dec
611 mkDeconstructor consName fs = do
612 d <- newName "d"
613 f <- newName "f"
614 fresh <- sequence [newName "a" | _<-fs]
615 fun (deconstructorName consName) (map varP [d, f])
616 [| $(pl (nameBase (deconstructorName consName)))
617 >-> $(pl (nameBase d))
618 >-> paren ($(pl ('\\':'(':nameBase consName))
619 >-> $lam >> printLit ")->"
620 >> $(hoas f))|]
621 where
622 lam = pl $ unwords [nameBase f | (f, _, _)<-fs]
623 hoas f = foldl appE (varE f)
624 [pl (nameBase f) | (f, _, _)<-fs]
625 \end{lstHaskell}
626
627 \subsubsection{Constructor predicates}
628 For the printer, the constructor selector for $C_k$ results in the following code \haskellinline{isCk f = printLit "isCk" >-> f}.
629
630 \begin{lstHaskell}
631 mkPredicate :: Name -> Q Dec
632 mkPredicate n = fun (predicateName n) []
633 [|\x-> $(pl $ nameBase $ predicateName n) >-> x|]
634 \end{lstHaskell}
635
636 \section{Pattern matching}
637 It is possible to construct and deconstruct values from other \gls{DSL} expressions, and to perform tests on the constructor but with a clunky and unwieldy syntax.
638 They have become first-class citizens in a grotesque way.
639 For example, given that we have some language constructs to denote failure and conditionals\footnotemark{}, writing a list summation function in our \gls{DSL} would be done as follows.
640 For the sake of the argument we take a little shortcut here and assume that the interpretation of the \gls{DSL} supports lazy evaluation by using the host language as a metaprogramming language as well, allowing us to use functions in the host language to contstruct expressions in the \gls{DSL}.
641
642 \begin{lrbox}{\LstBox}
643 \begin{lstHaskell}[frame=]
644 class Support v where
645 if' :: v Bool -> v a -> v a -> v a
646 bottom :: String -> v a
647 \end{lstHaskell}
648 \end{lrbox}
649 \footnotetext{\usebox{\LstBox}}
650
651 \begin{lstHaskell}
652 program :: (ListDSL v, Support v, ...) => Main (v Int)
653 program
654 = fun \sum->(\l->if'(isNil l)
655 (lit 0)
656 (unCons l (\hd tl->hd +. sum tl)))
657 :- Main (sum (cons (lit 38) (cons (lit 4) nil)))
658 \end{lstHaskell}
659
660 A similar \gls{HASKELL} implementation is much more elegant and less cluttered because of the support for pattern matching.
661 Pattern matching offers a convenient syntax for doing deconstruction and constructor tests at the same time.
662
663 \begin{lstHaskell}
664 sum :: List Int -> Int
665 sum Nil = 0
666 sum (List hd tl) = hd + sum tl
667
668 main = sum (Cons 38 (Cons 4 Nil))
669 \end{lstHaskell}
670
671 \subsection{Custom quasiquoters}
672 The syntax burden of \glspl{EDSL} can be reduced using quasiquotation.
673 In \gls{TH}, quasiquotation is a convenient way to create \gls{HASKELL} language constructs by entering them verbatim using Oxford brackets.
674 However, it is also possible to create so-called custom quasiquoters~\citep{mainland_why_2007}.
675 If the programmer writes down a fragment of code between tagged \emph{Oxford brackets}, the compiler executes the associated quasiquoter functions at compile time.
676 A quasiquoter is a value of the following data type:
677
678 \begin{lstHaskell}
679 data QuasiQuoter = QuasiQuoter
680 { quoteExp :: String -> Q Exp
681 , quotePat :: String -> Q Pat
682 , quoteType :: String -> Q Type
683 , quoteDec :: String -> Q Dec
684 }
685 \end{lstHaskell}
686
687 The code between \emph{dsl} brackets (\haskellinline{[dsl\|...\|]}) is preprocessed by the \haskellinline{dsl} quasiquoter.
688 Because the functions are executed at compile time, errors---thrown using the \haskellinline{MonadFail} instance of the \haskellinline{Q} monad---in these functions result in compile time errors.
689 The AST nodes produced by the quasiquoter are inserted into the location and checked as if they were written by the programmer.
690
691 To illustrate writing a custom quasiquoter, we show an implementation of a quasiquoter for binary literals.
692 The \haskellinline{bin} quasiquoter is only defined for expressions and parses subsequent zeros and ones as a binary number and splices it back in the code as a regular integer.
693 Thus, \haskellinline{[bin\|101010\|]} results in the literal integer expression \haskellinline{42}.
694 If an invalid character is used, a compile-time error is shown.
695 The quasiquoter is defined as follows:
696
697 \begin{lstHaskell}
698 bin :: QuasiQuoter
699 bin = QuasiQuoter { quoteExp = parseBin }
700 where
701 parseBin :: String -> Q Exp
702 parseBin s = LitE . IntegerL <$> foldM bindigit 0 s
703
704 bindigit :: Integer -> Char -> Q Integer
705 bindigit acc '0' = pure (2 * acc)
706 bindigit acc '1' = pure (2 * acc + 1)
707 bindigit acc c = fail ("invalid char: " ++ show c)
708 \end{lstHaskell}
709
710 \subsection{Quasiquotation for pattern matching}
711 Custom quasiquoters allow the \gls{DSL} user to enter fragments verbatim, bypassing the syntax of the host language.
712 Pattern matching in general is not suitable for a custom quasiquoter because it does not really fit in one of the four syntactic categories for which custom quasiquoter support is available.
713 However, a concrete use of pattern matching, interesting enough to be beneficial, but simple enough for a demonstration is the \emph{simple case expression}, a case expression that does not contain nested patterns and is always exhaustive.
714 They correspond to a multi-way conditional expressions and can thus be converted to \gls{DSL} constructs straightforwardly~\cite[Chp.~4.4]{peyton_jones_implementation_1987}.
715
716 In contrast to the binary literal quasiquoter example, we do not create the parser by hand.
717 The parser combinator library \emph{parsec} is used instead to ease the creation of the parser~\citep{leijen_parsec_2001}.
718 First the location of the quasiquoted code is retrieved using the \haskellinline{location} function that operates in the \haskellinline{Q} monad.
719 This location is inserted in the parsec parser so that errors are localised in the source code.
720 Then, the \haskellinline{expr} parser is called that returns an \haskellinline{Exp} in the \haskellinline{Q} monad.
721 The \haskellinline{expr} parser uses parsec's commodity expression parser primitive \haskellinline{buildExpressionParser}.
722 The resulting parser translates the string directly into \gls{TH}'s AST data types in the \haskellinline{Q} monad.
723 The most interesting parser is the parser for the case expression that is an alternative in the basic expression parser \haskellinline{basic}.
724 A case expression is parsed when a keyword \haskellinline{case} is followed by an expression that is in turn followed by a non-empty list of matches.
725 A match is parsed when a pattern (\haskellinline{pat}) is followed by an arrow and an expression.
726 The results of this parser are fed into the \haskellinline{mkCase} function that transforms the case into an expression using \gls{DSL} primitives such as conditionals, deconstructors and constructor predicates.
727 The above translates to the following skeleton implementation:
728
729 \begin{lstHaskell}
730 expr :: Parser (Q Exp)
731 expr = buildExpressionParser [...] basic
732 where
733 basic :: Parser (Q Exp)
734 basic = ...
735 <|> mkCase <$ reserved "case" <*> expr
736 <* reserved "of" <*> many1 match
737 <|> ...
738
739 match :: Parser (Q Pat, Q Exp)
740 match = (,) <$> pat <* reserved "->" <*> expr
741
742 pat :: Parser (Q Pat)
743 pat = conP <$> con <*> many var
744 \end{lstHaskell}
745
746 Case expressions are transformed into constructors, deconstructors and constructor predicates, e.g.\ \haskellinline{case e1 of Cons hd tl -> e2; Nil -> e3;} is converted to:
747 \begin{lstHaskell}
748 if' (isList e1)
749 (unCons e1 (\hd tl->e2))
750 (if' (isNil e1)
751 (unNil e1 e3)
752 (bottom "Exhausted case"))
753 \end{lstHaskell}
754
755 The \haskellinline{mkCase} (\cref{mkcase_fcd:mkcase}) function transforms a case expression into constructors, deconstructors and constructor predicates.
756 \Cref{mkcase_fcd:eval} first evaluates the patterns.
757 Then the patterns and their expressions are folded using the \haskellinline{mkCase`} function (\cref{mkcase_fcd:pairs}).
758 While a case exhaustion error is used as the initial value, this is never called since all case expressions are exhaustive.
759 For every case, code is generated that checks whether the constructor used in the pattern matches the constructor of the value using constructor predicates (\cref{mkcase_fcd:conspred}).
760 If the constructor matches, the deconstructor (\cref{mkcase_fcd:consdec}) is used to bind all names to the correct identifiers and evaluate the expression.
761 If the constructor does not match, the continuation (\haskellinline{\$rest}) is used (\cref{mkcase_fcd:consstart}).
762
763 \begin{lstHaskell}[numbers=left]
764 mkCase :: Q Exp -> [(Q Pat, Q Exp)] -> Q Exp [+\label{mkcase_fcd:mkcase} +]
765 mkCase name cases = do
766 pats <- mapM fst cases [+ \label{mkcase_fcd:eval} +]
767 foldr (uncurry mkCase') [|bottom "Exhausted case"|][+ \label{mkcase_fcd:fold}\label{mkcase_fcd:foldinit} +]
768 (zip pats (map snd cases)) [+\label{mkcase_fcd:pairs}+]
769 where
770 mkCase' :: Pat -> Q Exp -> Q Exp -> Q Exp
771 mkCase' (ConP cons fs) e rest
772 = [|if' $pred $then_ $rest|] [+\label{mkcase_fcd:consstart}+]
773 where
774 pred = varE (predicateName cons) `appE` name[+\label{mkcase_fcd:conspred}+]
775 then_ = [|$(varE (deconstructorName cons))[+\label{mkcase_fcd:consdec}+]
776 $name $(lamE [pure f | f<-fs] e)|][+\label{mkcase_fcd:consend}+]
777 \end{lstHaskell}
778
779 Finally, with this quasiquotation mechanism we can define our list summation using a case expression.
780 As a byproduct, syntactic cruft such as the special symbols for the operators and calls to \haskellinline{lit} can be removed as well resulting in the following summation implementation:
781
782 \begin{lstHaskell}
783 program :: (ListDSL v, DSL v, ...) => Main (v Int)
784 program
785 = fun \sum->(\l->[dsl|case l of
786 Cons hd tl -> hd + sum tl
787 Nil -> 0|])
788 :- Main (sum (cons (lit 38) (cons (lit 4) nil)))
789 \end{lstHaskell}
790
791 \section{Related work}
792 Generic or polytypic programming is a promising technique at first glance for automating the generation of function implementations~\citep{lammel_scrap_2003}.
793 However, while it is possible to define a function that works on all first-order types, adding a new function with a new name to the language is not possible.
794 This does not mean that generic programming is not useable for embedding pattern matches.
795 In generic programming, types are represented as sums of products and using this representation it is possible to define pattern matching functions.
796
797 For example, Rhiger showed a method for expressing statically typed pattern matching using typed higher-order functions~\citep{rhiger_type-safe_2009}.
798 If not the host language but the \gls{DSL} contains higher order functions, the same technique could be applied to port pattern matching to \glspl{DSL} though using an explicit sums of products representation.
799 Atkey et al.\ describe embedding pattern matching in a \gls{DSL} by giving patterns an explicit representation in the \gls{DSL} by using pairs, sums and injections~\cite[Section~3.3]{atkey_unembedding_2009}.
800
801 McDonell et al.\ extends on this idea, resulting in a very similar but different solution to ours~\citep{mcdonell_embedded_2021}.
802 They used the technique that Atkey et al.\ showed and applied it to deep embedding using the concrete syntax of the host language.
803 The scaffolding---e.g.\ generating the pairs, sums and injections---for embedding is automated using generics but the required pattern synonyms are generated using \gls{TH}.
804 The key difference to our approach is that we specialise the implementation for each of the backends instead of providing a general implementation of data type handling operations.
805 Furthermore, our implementation does not require a generic function to trace all constructors, resulting in problems with (mutual) recursion.
806
807 Young et al.\ added pattern matching to a deeply \gls{EDSL} using a compiler plugin~\citep{young_adding_2021}.
808 This plugin implements an \haskellinline{externalise :: a -> E a} function that allows lifting all machinery required for pattern matching automatically from the host language to the \gls{DSL}.
809 Under the hood, this function translates the pattern match to constructors, deconstructors, and constructor predicates.
810 The main difference with this work is that it requires a compiler plugin while our metaprogramming approach works on any compiler supporting a metaprogramming system similar to \gls{TH}.
811
812 \subsection{Related work on \gls{TH}}
813 Metaprogramming in general is a very broad research topic and has been around for years already.
814 We therefore do not claim an exhaustive overview of related work on all aspects of metaprogramming.
815 However, we have have tried to present most research on metaprogramming in \gls{TH}.
816 Czarnecki et al.\ provide a more detailed comparison of different metaprogramming techniques.
817 They compare staged interpreters, metaprogramming and templating by comparing MetaOCaml, \gls{TH} and C++ templates~\citep{czarnecki_dsl_2004}.
818 \gls{TH} has been used to implement related work.
819 They all differ slightly in functionality from our domain and can be divided into several categories.
820
821 \subsubsection{Generating extra code}
822 Using \gls{TH} or other metaprogramming systems it is possible to add extra code to your program.
823 The original \gls{TH} paper showed that it is possible to create variadic functions such as \haskellinline{printf} using \gls{TH} that would be almost impossible to define without~\citep{sheard_template_2002}.
824 Hammond et al.\ used \gls{TH} to generate parallel programming skeletons~\citep{hammond_automatic_2003}.
825 In practise, this means that the programmer selects a skeleton and, at compile time, the code is massaged to suit the pattern and information about the environment is inlined for optimisation.
826
827 Polak et al.\ implemented automatic GUI generation using \gls{TH}~\citep{polak_automatic_2006}.
828 Dureg\aa{}rd et al.\ wrote a parser generator using \gls{TH} and the custom quasiquoting facilities~\citep{duregard_embedded_2011}.
829 From a specification of the grammar, given in verbatim using a custom quasiquoter, a parser is generated at compile time.
830 Shioda et al.\ used metaprogramming in the D programming language to create a \gls{DSL} toolkit~\citep{shioda_libdsl_2014}.
831 They also programmatically generate parsers and a backend for either compiling or interpreting the \gls{IR}.
832 Blanchette et al.\ use \gls{TH} to simplify the development of Liquid \gls{HASKELL} proofs~\citep{blanchette_liquid_2022}.
833 Folmer et al.\ used \gls{TH} to synthesize C$\lambda$aSH~\citep{baaij_digital_2015} abstract syntax trees to be processed~\citep{folmer_high-level_2022}.
834 In similar fashion, Materzok used \gls{TH} to translate YieldFSM programs to C$\lambda$aSH~\citep{materzok_generating_2022}.
835
836 \subsubsection{Optimisation}
837 Besides generating code, it is also possible to analyse existing code and perform optimisations.
838 Yet, this is dangerous territory because unwantedly the semantics of the optimised program may be slightly different from the original program.
839 For example, Lynagh implemented various optimisations in \gls{TH} such as automatic loop unrolling~\citep{lynagh_unrolling_2003}.
840 The compile-time executed functions analyse the recursive function and unroll the recursion to a fixed depth to trade execution speed for program space.
841 Also, O'Donnell embedded Hydra, a hardware description language, in \gls{HASKELL} utilising \gls{TH}~\citep{odonnell_embedding_2004}.
842 Using intensional analysis of the AST, it detects cycles by labelling nodes automatically so that it can generate \emph{netlists}.
843 The authors mention that alternatively this could have be done using a monad but this hampers equational reasoning greatly, which is a key property of Hydra.
844 Finally, Viera et al.\ present a way of embedding attribute grammars in \gls{HASKELL} in a staged fashion~\citep{viera_staged_2018}.
845 Checking several aspects of the grammar is done at compile time using \gls{TH} while other safety checks are performed at runtime.
846
847 \subsubsection{Compiler extension}
848 Sometimes, expressing certain functionalities in the host languages requires a lot of boilerplate, syntax wrestling, or other pains.
849 Metaprogramming can relieve some of this stress by performing this translation to core constructs automatically.
850 For example, implementing generic---or polytypic--- functions in the compiler is a major effort.
851 Norell et al.\ used \gls{TH} to implement the machinery required to implement generic functions at compile time~\citep{norell_prototyping_2004}.
852 Adams et al.\ also explores implementing generic programming using \gls{TH} to speed things up considerably compared to regular generic programming~\citep{adams_template_2012}.
853 Clifton et al.\ use \gls{TH} with a custom quasiquoter to offer skeletons for workflows and embed foreign function interfaces in a \gls{DSL}~\citep{clifton-everest_embedding_2014}.
854 Eisenberg et al.\ showed that it is possible to programmatically lift some functions from the function domain to the type domain at compile time, i.e.\ type families~\citep{eisenberg_promoting_2014}.
855 Furthermore, Seefried et al.\ argued that it is difficult to do some optimisations in \glspl{EDSL} and that metaprogramming can be of use there~\citep{seefried_optimising_2004}.
856 They use \gls{TH} to change all types to unboxed types, unroll loops to a certain depth and replace some expressions by equivalent more efficient ones.
857 Torrano et al.\ showed that it is possible to use \gls{TH} to perform a strictness analysis and perform let-to-case translation~\citep{torrano_strictness_2005}.
858 Both applications are examples of compiler extensions that can be implemented using \gls{TH}.
859 Another example of such a compiler extension is shown by Gill et al.~\citep{gill_haskell_2009}.
860 They created a meta level \gls{DSL} to describe rewrite rules on \gls{HASKELL} syntax that are applied on the source code at compile time.
861
862 \subsubsection{Quasiquotation}
863 By means of quasiquotation, the host language syntax that usually seeps through the embedding can be hidden.
864 The original \gls{TH} quasiquotation paper~\citep{mainland_why_2007} shows how this can be done for regular expressions, not only resulting in a nicer syntax but syntax errors are also lifted to compile time instead of run time.
865 Also, Kariotis et al.\ used \gls{TH} to automatically construct monad stacks without having to resort to the monad transformers library which requires advanced type system extensions~\citep{kariotis_making_2008}.
866
867 Najd uses the compile time to be able to do normalisation for a \gls{DSL}, dubbing it \glspl{QDSL}~\citep{najd_everything_2016}.
868 They utilise the quasiquation facilities of \gls{TH} to convert \gls{HASKELL} \gls{DSL} code to constructs in the \gls{DSL}, applying optimisations such as eliminating lambda abstractions and function applications along the way.
869 Egi et al.\ extended \gls{HASKELL} to support non-free data type pattern matching---i.e.\ data type with no standard form, e.g.\ sets, graphs---using \gls{TH}~\citep{egi_embedding_2022}.
870 Using quasiquotation, they make a complicated embedding of non-linear pattern matching available through a simple lens.
871
872 \subsubsection{\gls{TTH}}\label{ssec_fcd:typed_template_haskell}
873 \gls{TTH} is a very recent extension/alternative to normal \gls{TH}~\citep{pickering_multi-stage_2019,xie_staging_2022}.
874 Where in \gls{TH} you can manipulate arbitrary parts of the syntax tree, add top-level splices of data types, definitions and functions, in \gls{TTH} the programmer can only splice expressions but the abstract syntax tree fragments representing the expressions are well-typed by construction instead of untyped.
875
876 Pickering et al.\ implemented staged compilation for the \emph{generics-sop}~\citep{de_vries_true_2014} generics library to improve the efficiency of the code using \gls{TTH}~\citep{pickering_staged_2020}.
877 Willis et al.\ used \gls{TTH} to remove the overhead of parsing combinators~\citep{willis_staged_2020}.
878
879 \section{Discussion}
880 Functional programming languages are especially suitable for embedding \glspl{DSL} but adding user-defined data types is still an issue.
881 The tagless-final style of embedding offers great modularity, extensibility and flexibility.
882 However, user-defined data types are awkward to handle because the built-in operations on them---construction, deconstruction and constructor tests---are not inherited from the host language.
883 By giving an implementation, we showed how to create a \gls{TH} function that will splice the required class definitions and view instances.
884 The code dataset also contains an implementation for defining field selectors and provides an implementation for a compiler (see \cref{chp:research_data_management}).
885 Furthermore, by writing a custom quasiquoter, pattern matches in natural syntax can be automatically converted to the internal representation of the \gls{DSL}, thus removing the syntax burden of the facilities.
886 The use of a custom quasiquoter does require the \gls{DSL} programmer to write a parser for their \gls{DSL}, i.e.\ the parser is not inherited from the host language as is often the case in an embedded \gls{DSL}.
887 However, by making use of modern parser combinator libraries, this overhead is limited and errors are already caught at compilation.
888
889 \subsection{Future work}
890 For future work, it would be interesting to see how generating boilerplate for user-defined data types translates from shallow embedding to deep embedding.
891 In deep embedding, the language constructs are expressed as data types in the host language.
892 Adding new constructs, e.g.\ constructors, deconstructors, and constructor tests, for the user-defined data type therefore requires extending the data type.
893 Techniques such as data types \`a la carte~\citep{swierstra_data_2008} and open data types~\citep{loh_open_2006} show that it is possible to extend data types orthogonally but whether metaprogramming can still readily be used is something that needs to be researched.
894 It may also be possible to implemented (parts) of the boilerplate generation using \gls{TTH} (see \cref{ssec_fcd:typed_template_haskell}) to achieve more confidence in the type correctness of the implementation.
895
896 Another venue of research is to try to find the limits of this technique regarding richer data type definitions.
897 It would be interesting to see whether it is possible to apply the technique on data types with existentially quantified type variables or full-fledged generalised \glspl{ADT}~\citep{hinze_fun_2003}.
898 It is not possible to straightforwardly lift the deconstructors to type classes because existentially quantified type variables will escape.
899 Rank-2 polymorphism offers tools to define the types in such a way that this is not the case anymore.
900 However, implementing compiling views on the \gls{DSL} is complicated because it would require inventing values of an existentially quantified type variable to satisfy the type system which is difficult.
901
902 Finally, having to write a parser for the \gls{DSL} is extra work.
903 Future research could determine whether it is possible to generate this using \gls{TH} as well.
904
905 \input{subfilepostamble}
906 \end{document}