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