22b9977febb5891791e4cc18b0d0ffe1b9fbbd9b
[phd-thesis.git] / dsl / dsl_techniques.tex
1 \documentclass[../thesis.tex]{subfiles}
2
3 \include{subfilepreamble}
4
5 \begin{document}
6 \chapter{\texorpdfstring{\Glsxtrshort{DSL}}{DSL} embedding techniques}%
7 \label{chp:dsl_embedding_techniques}%
8 An \gls{EDSL} is a language embedded in a host language created for a specific domain\todo{citation needed?}.
9 Properties such as referential transparency, minimal syntax, powerful type systems and rich data types make \gls{FP} languages excellent candidates for hosting \glspl{EDSL}.
10
11 There are two flavours of \gls{DSL} embedding: deep- and shallow embedding~\citep{boulton_experience_1992}.
12 Shallow embedding---also called tagless embedding---models language constructs as functions in the host language.
13 As a result, adding new language constructs---extra functions---is easy.
14 However, the interpretation of the language is embedded in these functions, making it troublesome to add semantics since it requires updating all existing language constructs.
15
16 In contrast to shallow embedding, deep embedding---also called tagged embedding---models terms in the language as data types.
17 Interpretations are functions over these data types.
18
19 Consequently, adding new semantics, i.e.\ novel functions, is straightforward.
20 It can be stated that the language constructs are embedded in the functions that form a semantics.
21 If one wants to add a language construct, all semantics functions must be revisited and revised to avoid ending up with partial functions.
22
23 This juxtaposition has been known for many years~\citep{reynolds_user-defined_1978} and discussed by many others~\citep{krishnamurthi_synthesizing_1998} but most famously dubbed the \emph{expression problem} by Wadler~\citep{wadler_expression_1998}:
24
25 \begin{quote}
26 The \emph{expression problem} is a new name for an old problem.
27 The goal is to define a data type by cases, where one can add new cases to the data type and new functions over the data type, without recompiling existing code, and while retaining static type safety (e.g., no casts).
28 \end{quote}
29
30 Terms in an \glspl{EDSL} can have multiple interpretations\footnote{Interpretations are also called backends or views}, i.e.\ a term in the \gls{DSL} is just an interface.
31 Commonly used intepretations are printing, compiling, simulating, optimising, verifying, proving the program\etc.
32 There are two main flavours of embedding \glspl{DSL}.
33 Deep embedding---also called shallow--- models terms in the language as data types, interpretations are functions over these terms.
34 Shallow embedding---also called tagless---models terms in the language as functions, interpretations are embedded in these functions.
35
36 Most importantly, the two flavours differ on two axes: extensibility of language constructs and extensibility of interpretations.
37 \todo{elaborate}
38
39 \Cref{sec:deep_embedding} shows the basics of deep embedding.
40 \Cref{sec:shallow_embedding} shows the basics of shallow embedding including tagless embedding.
41 \Cref{sec:compare_embedding} compares the embedding technique.
42
43 In the following sections the basics of both techniques are explained.
44 A simple language with integers, booleans and some arithmetic operators is used as a running example.
45
46 \section{Deep embedding}\label{sec:deep_embedding}
47 In a deeply embedded \gls{DSL}, the language terms are represented as data type{(s)} in the host language.
48 Therefore, interpretations of the terms are functions that operate on these data types.
49 \Cref{lst:exdeep} shows an implementation for the example \gls{DSL}.
50
51 \begin{lstHaskell}[label={lst:exdeep},caption={A deeply embedded expression \gls{DSL}.}]
52 data Value = I Int | B Bool
53 data Expr
54 = Lit Value
55 | Plus Expr Expr
56 | Eq Expr Expr
57 deriving Show
58 \end{lstHaskell}
59
60 Implementing a printer for the language is straightforward, we just define a function that transforms the term to a string.
61
62 \begin{lstHaskell}[caption={A printer for the deeply embedded expression \gls{DSL}.}]
63 print :: Expr -> String
64 print (Lit i) = show i
65 print (Plus l r) = "(" ++ print l ++ "+" ++ print r ++ ")"
66 print (Eq l r) = "(" ++ print l ++ "==" ++ print r ++ ")"
67 \end{lstHaskell}
68
69 Adding a construct---for example subtraction---reveals the Achilles' heel of deep embedding, namely that we need to revisit the original data type \emph{and} all the existing views.
70 I.e.\ we need to add \haskellinline{| Sub Expr Expr} to the \haskellinline{Expr} data type.
71 Furthermore, we need to add \haskellinline{print (Sub l r) = ...} to the \haskellinline{print} view in order to not end up with a partial function.
72 This limitation can be overcome by lifting the views to classes (See \cref{chp:classy_deep_embedding}).
73
74 Implementing an evaluator for the language is possible without touching any original code, we just add a function operating on the \haskellinline{Expr} data type.
75 To store variables, it has an extra environment argument.
76 Here another downside of basic deep embedding arises immediately, the expressions are not typed, and therefore there has to be some type checking in the evaluation code.
77 Luckily this problem can be overcome by switching from regular \glspl{ADT} to \glspl{GADT}, resulting in the following data type and evaluator.
78
79 \begin{lstHaskell}[caption={An evaluator for the deeply embedded expression \gls{DSL}.}]
80 eval :: Expr -> Value
81 eval (Lit i) = i
82 eval (Plus l r) = case (eval l, eval r) of
83 (Lit (I l), Lit (I r)) -> I (l+r))
84 (l, r) -> error ("Can't add " ++ show l ++ " to " ++ show r)
85 eval (Eq l r) = case (eval l, eval r) of
86 (Lit (I l), Lit (I r)) -> B (l==r)
87 (Lit (B l), Lit (B r)) -> B (l==r)
88 (l, r) -> error ("Can't compare " ++ show l ++ " to " ++ show r)
89 \end{lstHaskell}
90
91 \subsection{\texorpdfstring{\Glsxtrlongpl{GADT}}{Generalised algebraic data types}}
92 Deep embedding has the advantage that it is easy to build and views are easy to add.
93 On the downside, the expressions created with this language are not necessarily type-safe.
94 In the given language it is possible to create an expression such as \haskellinline{LitI 4 `Plus` LitB True} that adds a boolean to an integer.
95 Extending the \gls{ADT} is easy and convenient but extending the views accordingly is tedious since it has to be done individually for all views.
96
97 The first downside of this type of \gls{EDSL} can be overcome by using \glspl{GADT}~\citep{cheney_first-class_2003}.
98 \Cref{lst:exdeepgadt} shows the same language, but type-safe with a \gls{GADT}.
99 \glspl{GADT} are not supported in the current version of \gls{CLEAN} and therefore the syntax is hypothetical (See \todo{insert link to appendix}).
100 However, it has been shown that \glspl{GADT} can be simulated using bimaps or projection pairs~\citep[\citesection{2.2}]{cheney_lightweight_2002}.
101 Unfortunately the lack of extendability remains a problem.
102 If a language construct is added, no compile time guarantee can be given that all views support it.
103
104 \begin{lstHaskell}[label={lst:exdeepgadt},caption={A deeply embedded expression \gls{DSL} using \glspl{GADT}.}]
105 data Expr a where
106 Lit :: Show a => a -> Expr a
107 Plus :: Num a => Expr a -> Expr a -> Expr a
108 Eq :: Eq a => Expr a -> Expr a -> Expr Bool
109
110 eval :: Expr a -> a
111 eval (Lit i) = i
112 eval (Plus l r) = eval l + eval r
113 eval (Eq l r) = eval l == eval r
114 \end{lstHaskell}
115
116 \section{Shallow embedding}\label{sec:shallow_embedding}
117 In a shallowly \gls{EDSL} all language constructs are expressed as functions in the host language.
118 An evaluator view for the example language then can be implemented as the code shown in \cref{lst:exshallow}.
119 Note that the internals of the language could have been hidden using a reader monad.
120
121 \begin{lstHaskell}[label={lst:exshallow}, caption={A minimal shallow \gls{EDSL}.}]
122 type Env = String -> Int
123 type DSL a = Env -> a
124
125 lit :: a -> DSL a
126 lit x = \e->x
127
128 var :: String -> DSL Int
129 var i = \e->retrEnv e i
130
131 plus :: DSL Int -> DSL Int -> DSL Int
132 plus x y = \e->x e + y e
133
134 eq :: Eq a => DSL a -> DSL a -> DSL Bool
135 eq x y = \e->x e == y e
136 \end{lstHaskell}
137
138 One of the advantages of shallowly embedding a language in a host language is its extendability.
139 It is very easy to add functionality because the compile time checks of the host language guarantee whether or not the functionality is available when used.
140 For example, adding a new construct---such as subtraction---is done as follows:
141
142 \begin{lstHaskell}[label={lst:exshallowsubst},caption={Adding subtraction to the shallow \gls{EDSL}.}]
143 sub :: DSL Int -> DSL Int -> DSL Int
144 sub x y = \e->x e - y e
145 \end{lstHaskell}
146
147 Moreover, the language is type safe as it is directly typed in the host language, i.e.\ \haskellinline{lit True `plus` lit 4} is rejected.
148 Another advantage is the intimate link with the host language, allowing for a lot more linguistic reuse such as the support of implicit sharing \citep{krishnamurthi_linguistic_2001}.
149
150 The downside of this method is extending the language with views.
151 It is nearly impossible to add views to a shallowly embedded language.
152 The only way of achieving this is by reimplementing all functions so that they run all backends at the same time or to create a single interpretation that produces a fold function \citep{gibbons_folding_2014}.
153
154 \subsection{Tagless-final embedding}\label{ssec:tagless}
155 By lifting the functions representing the \gls{DSL} terms to type classes, interpretations can be added.
156 This technique is called tagless-final---or class-based shallow---embedding.
157 The interface for the \gls{DSL} looks as follows:
158
159 \begin{lstHaskell}[label={lst:extagless},caption={A minimal tagless-final \gls{EDSL}.}]
160 class DSL v where
161 lit :: a -> v a
162 var :: String -> v a
163 plus :: v Int -> v Int -> v Int
164 eq :: Eq a => v a -> v a -> v Bool
165 \end{lstHaskell}
166
167 An interpretation of this view is a data type that implements the type class.
168
169 \begin{lstHaskell}[label={lst:extaglessprint},caption={A pretty printer for the tagless-final \gls{EDSL}.}]
170 data Print a = P {runPrint :: String}
171 instance DSL Print where
172 lit a = P (show a)
173 var i = P i
174 plus x y = P ("(" ++ runPrint x ++ "+" ++ runPrint y ++ ")"
175 eq x y = P ("(" ++ runPrint x ++ "==" ++ runPrint y ++ ")"
176 \end{lstHaskell}
177
178 Adding a language construct---e.g.\ subtraction---is a easy as adding a type class and providing instances for interpretations.
179
180 \begin{lstHaskell}[label={lst:extaglesssubt},caption={Adding subtraction to the shallow \gls{EDSL}.}]
181 class Sub v where
182 sub :: v Int -> v Int -> v Int
183
184 instance Sub Print where
185 sub x y = P ("(" ++ runPrint x ++ "-" ++ runPrint y ++ ")"
186 \end{lstHaskell}
187
188 Adding an interpretation means adding a data type and providing instances for the language constructs.
189
190 \begin{lstHaskell}[label={lst:extaglesseval},caption={An evaluator interpretation of the minimal tagless-final \gls{EDSL}.}]
191 data Eval a = Eval {runEval :: Env -> a}
192
193 instance DSL v where
194 lit a = Eval (\_->a)
195 var i = Eval (\e->retrEnv e i)
196 plus x y = Eval (\e->runEval x e + runEval y e)
197 eq x y = Eval (\e->runEval x e == runEval y e)
198
199 instance Sub Eval where
200 sub x y = Eval (\e->runEval x e - runEval y e)
201 \end{lstHaskell}
202
203 \section{Comparison}\label{sec:compare_embedding}
204 Both flavours have their strengths and weaknesses and both flavours can be improved in order to mitigate (some of the) downsides.
205
206 \begin{table}[ht]
207 \begin{threeparttable}[b]
208 \caption{Comparison of embedding techniques, adapted from \citet[\citesection{3.6}]{sun_compositional_2022}}%
209 \label{tbl:dsl_comparison}
210 \begin{tabular}{lllllll}
211 \toprule
212 & Shallow & Deep & Hybrid & Poly & Comp. & Classy\\
213 \midrule
214 Transcoding free & yes & yes & no & yes & yes & yes\\
215 Linguistic reuse & yes & no & partly\tnote{1} & yes & yes & no?\\
216 Extend constructs & yes & no & partly\tnote{1} & yes & yes & yes\\
217 Extend interpretations & no & yes & yes & yes & yes & yes\\
218 Transformations & no & yes & yes & maybe\tnote{2} & maybe\tnote{2} & yes\\
219 Modular dependencies & no & maybe & maybe & yes & yes & yes\\
220 Nested pattern matching & no & yes & yes & no & maybe & maybe\tnote{3}\\
221 Type safe & yes & maybe & no & yes & yes & yes\\
222 \bottomrule
223 \end{tabular}
224 \begin{tablenotes}
225 \item [1] Only in the shallowly embedded part.
226 \item [2] Transformations require some ingenuity and are sometimes awkward to write.
227 \item [3] It requires some---safe---form of dynamic typing.
228 \end{tablenotes}
229 \end{threeparttable}
230 \end{table}
231
232 \input{subfilepostamble}
233 \end{document}