This paper was converted on www.awesomepapers.org from LaTeX by an anonymous user.
Want to know more? Visit the Converter page.

11institutetext: University of Massachusetts Lowell, Lowell MA 01854, USA

A Calculus for Language Transformations

Benjamin Mourad    Matteo Cimini
Abstract

In this paper we propose a calculus for expressing algorithms for programming languages transformations. We present the type system and operational semantics of the calculus, and we prove that it is type sound. We have implemented our calculus, and we demonstrate its applicability with common examples in programming languages. As our calculus manipulates inference systems, our work can, in principle, be applied to logical systems.

1 Introduction

Operational semantics is a standard de facto to defining the semantics of programming languages [PLOTKIN]. However, producing a programming language definition is still a hard task. It is not surprising that theoretical and software tools for supporting the modeling of languages based on operational semantics have received attention in research [LangWorkbenches, Rosu2010, Redex]. In this paper, we address an important aspect of language reuse which has not received attention so far: Producing language definitions from existing ones by the application of transformation algorithms. Such algorithms may automatically add features to the language, or switch to different semantics styles. In this paper, we aim at providing theoretical foundations and a software tool for this aspect.

Consider the typing rule of function application below on the left and its version with algorithmic subtyping on the right.

(t-app)        Γe1:T1T2   Γe2:T1      Γe1e2:T2 f(t-app) (t-app’)          Γe1:T11T2   Γe2:T12 T12<:T11      Γe1e2:T2 \displaystyle{{\vbox{\hbox{\hbox{\small\small{(t-app)}}}\hbox{$\displaystyle\displaystyle{\hbox{\hskip 55.66817pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:T_{1}\to T_{2}$}\hskip 17.00024pt\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{2}:T_{1}$}}}}\vbox{}}}\over\hbox{\hskip 24.62883pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}\;e_{2}:T_{2}$}}}}}}$}}}}~~\stackrel{{\scriptstyle f(\textsc{t-app})}}{{\Longrightarrow}}~~{{\vbox{\hbox{\hbox{\small\small{(t-app')}}}\hbox{$\displaystyle\displaystyle{\hbox{\hskip 59.47934pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:T_{11}\to T_{2}$}\hskip 17.00024pt\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{2}:T_{12}$}}}}\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle T_{12}<:T_{11}$}}}\vbox{}}}}\over\hbox{\hskip 24.62883pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}\;e_{2}:T_{2}$}}}}}}$}}}}

Intuitively, we can describe (t-app’) as a function of (t-app). Such a function includes, at least, giving new variable names when a variable is mentioned more than once, and must relate the new variables with subtyping according to the variance of types (covariant vs contravariant). Our question is: Can we express, easily, language transformations in a safe calculus?

Language transformations are beneficial for a number of reasons. On the theoretical side, they isolate and make explicit the insights that underly some programming languages features or semantics style. On the practical side, language transformations do not apply just to one language but to several languages. They can alleviate the burden to language designers, who can use them to automatically generate new language definitions using well-established algorithms rather than manually defining them, an error prone endeavor.

In this paper, we make the following contributions.

  • We present Tr\mathcal{L}\textendash\textsf{Tr} (pronounced “Elter”), a formal calculus for language transformations (Section 2). We define the syntax (Section 2.1), operational semantics (Section 2.2), and type system (Section 2.3) of Tr\mathcal{L}\textendash\textsf{Tr}.

  • We prove that Tr\mathcal{L}\textendash\textsf{Tr} is type sound (Section 2.3).

  • We show the applicability of Tr\mathcal{L}\textendash\textsf{Tr} to the specification of two transformations: adding subtyping and switching from small-step to big-step semantics (Section 3). Our examples show that Tr\mathcal{L}\textendash\textsf{Tr} is expressive and offers a rather declarative style to programmers.

  • We have implemented Tr\mathcal{L}\textendash\textsf{Tr} [ltr], and we report that we have applied our transformations to several language definitions.

Related work are discussed in Section LABEL:related, and Section LABEL:conclusion concludes the paper.

2 A Calculus for Language Transformations

We focus on language definitions in the style of operational semantics. To briefly summarize, languages are specified with a BNF grammar and a set of inference rules. BNF grammars have grammar productions such as TypesT::=BTT\text{\sf Types}\;T::=B\mid\;T\to T. We call Types a category name, TT is a grammar meta-variable, and BB and TTT\to T, as well as, for example, (λx.ev)(\lambda x.e\;v), are terms. (λx.ev)e[v/x](\lambda x.e\;v)\longrightarrow e[v/x] and Γ(e1e2):T2\Gamma\vdash(e_{1}\;e_{2}):T_{2} are formulae. An inference rule \inferencef1,,fnf\inference{f_{1},\ldots,f_{n}}{f} has a set of formulae above the horizontal line, which are called premises, and a formula below the horizontal line, which is called the conclusion.

2.1 Syntax of Tr\mathcal{L}\textendash\textsf{Tr}

Below we show the Tr\mathcal{L}\textendash\textsf{Tr} syntax for language definitions, which reflects the operational semantics style of defining languages. Sets are accommodated with lists.

cnameCatName,XMeta-Var,opnameOpName,prednamePredNamecname\in\textsc{CatName},{X}\in\textsc{Meta-Var},opname\in\textsc{OpName},predname\in\textsc{PredName}

Language::=(G,R)GrammarG::={s1,,sn}Grammar Pr.s::=cnameX::=ltRuler::=\inferencelffFormulaf::=prednameltTermt::=Xopnamelt(X)tt[t/X]List of RulesR::=𝚗𝚒𝚕𝚌𝚘𝚗𝚜rRList of Formula𝑙𝑓::=𝚗𝚒𝚕𝚌𝚘𝚗𝚜f𝑙𝑓List of Termslt::=𝚗𝚒𝚕𝚌𝚘𝚗𝚜tlt\begin{array}[]{l@{\;\;}lcl}\text{\sf Language}&\mathcal{L}&::=&(G,R)\\ \text{\sf Grammar}&G&::=&\{s_{1},\ldots,s_{n}\}\\ \text{\sf Grammar Pr.}&s&::=&cname\;{X}::=lt\\ \text{\sf Rule}&r&::=&\inference{lf}{f}\\ \text{\sf Formula}&f&::=&predname\;lt\\ \text{\sf Term}&t&::=&{X}\mid opname\;lt\mid({X})t\mid t[t/{X}]\\ \text{\sf List of Rules}&R&::=&{\mathtt{nil}}\mid\mathtt{cons}\;r\;R\\ \text{\sf List of Formula}&\mathit{lf}&::=&{\mathtt{nil}}\mid\mathtt{cons}\;f\;\mathit{lf}\\ \text{\sf List of Terms}&lt&::=&{\mathtt{nil}}\mid\mathtt{cons}\;t\;lt\end{array}

We assume a set of category names CatName, a set of meta-variables Meta-Var, a set of constructor operator names OpName, and a set of predicate names PredName. We assume that these sets are pairwise disjoint. OpName contains elements such as \to and λ\lambda (elements do not have to necessarily be (string) names). PredName contains elements such as \vdash and \longrightarrow. To facilitate the modeling of our calculus, we assume that terms and formulae are defined in abstract syntax tree fashion. Here this means that they always have a top level constructor applied to a list of terms. Tr\mathcal{L}\textendash\textsf{Tr} also provides syntax to specify unary binding (z)t({z})t and capture-avoiding substitution t[t/z]t[t/{z}]. Therefore, Tr\mathcal{L}\textendash\textsf{Tr} is tailored for static scoping rather than dynamic scoping. Lists can be built as usual with the 𝚗𝚒𝚕{\mathtt{nil}} and 𝚌𝚘𝚗𝚜\mathtt{cons} operator. We sometimes use the shorthand [o1,on][o_{1},\ldots o_{n}] for the corresponding series of 𝚌𝚘𝚗𝚜\mathtt{{\mathtt{cons}}} applications ended with 𝚗𝚒𝚕{\mathtt{nil}}.

To make an example, the typing rule for function application and the β\beta-reduction rules are written as follows. (appapp is the top-level operator name for function application).

\inference[[Γ,e1,([T1,T2])],[Γ,e2,T1]][Γ,(app[e1,e2]),T2]\inference[][(app[(λ[(x)e]),v]),e[v/x]]\displaystyle\inference{[\;\vdash\;[{\Gamma},{e_{1}},(\to[{T_{1}},{T_{2}}])],\;\vdash\;[{\Gamma},{e_{2}},{T_{1}}]\;]}{\vdash\;[{\Gamma},(app\;[{e_{1}},{e_{2}}]),{T_{2}}]}\qquad\inference{[]}{\longrightarrow\;[(app\;[(\lambda\;[({x}){e}]),{v}]),{e}[{v}/x]]}

Below we show the rest of the syntax of Tr\mathcal{L}\textendash\textsf{Tr}.
xVar,strString,{𝑠𝑒𝑙𝑓,𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠,𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛}Varx\in\textsc{Var},str\in\textsc{String},\{\mathit{self},\mathit{premises},\mathit{conclusion}\}\subseteq\textsc{Var}

Expressione::=xcnamestrt^f^r^𝚗𝚒𝚕𝚌𝚘𝚗𝚜ee𝚑𝚎𝚊𝚍e𝚝𝚊𝚒𝚕ee@e𝚖𝚊𝚙(e,e)e(e)𝚖𝚊𝚙𝙺𝚎𝚢𝚜e𝚓𝚞𝚜𝚝e𝚗𝚘𝚝𝚑𝚒𝚗𝚐𝚐𝚎𝚝ecnameX::=ecnameX::=e𝚐𝚎𝚝𝚁𝚞𝚕𝚎𝚜𝚜𝚎𝚝𝚁𝚞𝚕𝚎𝚜ee[p]:ee(𝚔𝚎𝚎𝚙)[p]:e𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(e,e,str)(x,x):e𝚒𝚏b𝚝𝚑𝚎𝚗e𝚎𝚕𝚜𝚎ee;ee;re𝚜𝚔𝚒𝚙𝚗𝚎𝚠𝚅𝚊𝚛e𝚏𝚘𝚕𝚍prednamee𝚎𝚛𝚛𝚘𝚛Boolean Expr.b::=e==e𝚒𝚜𝙴𝚖𝚙𝚝𝚢ee𝚒𝚗e𝚒𝚜𝙽𝚘𝚝𝚑𝚒𝚗𝚐eb𝚊𝚗𝚍bb𝚘𝚛b𝚗𝚘𝚝bTr Ruler^::=\inferenceeeTr Formulaf^::=prednameexeTr Termt^::=Xopnameexe(X)ee[e/X]Patternp::=x:Tprednamepopnamepxp𝚗𝚒𝚕𝚌𝚘𝚗𝚜ppValuev::=tfrcnamestr𝚗𝚒𝚕𝚌𝚘𝚗𝚜vv𝚖𝚊𝚙(v,v)𝚓𝚞𝚜𝚝v𝚗𝚘𝚝𝚑𝚒𝚗𝚐𝚜𝚔𝚒𝚙\begin{array}[]{l@{\;\;}lcl}\text{\sf Expression}&e&::=&x\mid cname\mid str\mid{\hat{t}}\mid{\hat{f}}\mid{\hat{r}}\\ &&&\mid\mathtt{nil}\mid\mathtt{cons}\;e\;e\mid{\mathtt{head}}\;e\mid{\mathtt{tail}}\;e\mid e@e\\ &&&\mid\mathtt{map}(e,e)\mid e(e)\mid\mathtt{mapKeys}\;e\\ &&&\mid\mathtt{just}\;e\mid\mathtt{nothing}\mid\mathtt{get}\;e\\ &&&\mid cname\;{X}::=e\;\mid cname\;{X}::=\ldots\;e\\ &&&\mid\mathtt{getRules}\mid\mathtt{setRules}\;e\\ &&&\mid{e}[p]:\;e\mid{e(\mathtt{keep})}[p]:\;e\mid\mathtt{uniquefy}(e,e,str)\Rightarrow(x,x):e\\ &&&\mid\mathtt{if}\;b\;\mathtt{then}\;e\;\mathtt{else}\;e\mid e\;;\;e\mid e;_{\textsf{r}}e\mid\mathtt{skip}\\ &&&\mid\mathtt{newVar}\mid e\texttt{'}\mid\mathtt{fold}\;predname\;e\\ &&&\mid\mathtt{error}\\ \text{\sf Boolean Expr.}&b&::=&e==e\mid\mathtt{isEmpty}\;e\mid e\;\mathtt{in}\;e\mid\mathtt{isNothing}\;e\mid b\;\mathtt{and}\;b\mid b\;\mathtt{or}\;b\mid\mathtt{not}\;b\\ \text{\sf$\mathcal{L}\textendash\textsf{Tr}$ Rule}&{\hat{r}}&::=&\inference{e}{e}\\ \text{\sf$\mathcal{L}\textendash\textsf{Tr}$ Formula}&{\hat{f}}&::=&predname\;e\mid x\;e\\ \text{\sf$\mathcal{L}\textendash\textsf{Tr}$ Term}&{\hat{t}}&::=&{X}\mid opname\;e\mid x\;e\mid({X})e\mid e[e/{X}]\\ \text{\sf Pattern}&p&::=&x:T\mid predname\;p\mid opname\;p\mid x\;p\mid\mathtt{nil}\mid\mathtt{cons}\;p\;p\\ \text{\sf Value}&v&::=&{t}\mid{f}\mid{r}\mid cname\mid str\\ &&&\mid\mathtt{nil}\mid\mathtt{cons}\;v\;v\mid\mathtt{map}(v,v)\mid\mathtt{just}\;v\mid\mathtt{nothing}\mid\mathtt{skip}\end{array}

Programmers write expressions to specify transformations. At run-time, an expression will be executed with a language definition. Evaluating an expression may modify the current language definition.

Design Principles: We strive to offer well-crafted operations that map well with the language manipulations that are frequent in adding features to languages or switching semantics styles. There are three features that we can point out which exemplify our approach the most: 1) The ability to program parts of rules, premises and grammars, 2) selectors e[p]:e{e}[p]:\;e, and 3) the 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} operation. Below, we shall the describe the syntax for transformations, and place some emphasis in motivating these three operations.
Basic Data Types: Tr\mathcal{L}\textendash\textsf{Tr} has strings and has lists with typical operators for extracting their head and tail, as well as for concatenating them (@@). Tr\mathcal{L}\textendash\textsf{Tr} also has maps (key-value). In 𝚖𝚊𝚙(e1,e2)\mathtt{map}(e_{1},e_{2}), e1e_{1} and e2e_{2} are lists. The first element of e1e_{1} is the key for the first element of e2e_{2}, and so on for the rest of elements. Such a representation fits better our language transformations examples, as we shall see in Section 3. Operation e1(e2)e_{1}(e_{2}) queries a map, where e1e_{1} is a map and e2e_{2} is a key, and 𝚖𝚊𝚙𝙺𝚎𝚢𝚜e\mathtt{mapKeys}\;e returns the list of keys of the map ee. Maps are convenient in Tr\mathcal{L}\textendash\textsf{Tr} to specify information that is not expressible in the language definition. For example, we can use maps to store information about whether some type argument is covariant or contravariant, or to store information about the input-output mode of the arguments of relations. Section 3 shows that we use maps in this way extensively. Tr\mathcal{L}\textendash\textsf{Tr} also has options (𝚓𝚞𝚜𝚝\mathtt{just}, 𝚗𝚘𝚝𝚑𝚒𝚗𝚐\mathtt{nothing}, and 𝚐𝚎𝚝\mathtt{get}). We include options because they are frequently used in combination with the selector operator described below. Programmers can refer to grammar categories (cname) in positions where a list is expected. When cname is used the corresponding list of grammar items is retrieved.
Grammar Instructions: cnameX::=ecname\;{X}::=e is essentially a grammar production. With this instruction, the current grammar is augmented with this production. cnameX::=ecname\;{X}::=\ldots\;e (notice the dots) adds the terms in ee to an existing production. 𝚐𝚎𝚝𝚁𝚞𝚕𝚎𝚜\mathtt{getRules} and 𝚜𝚎𝚝𝚁𝚞𝚕𝚎𝚜e\mathtt{setRules}\;e retrieve and set the current list of rules, respectively.
Selectors: e1[p]:e2{e_{1}}[p]:\;e_{2} is the selector operator. This operation selects one by one the elements of the list e1e_{1} that satisfy the pattern pp and executes the body e2e_{2} for each of them. This operation returns a list that collects the result of each iteration. Selectors are useful for selecting elements of a language with great precision, and applying manipulations to them. To make an example, suppose that the variable prems contains the premises of a rule and that we wanted to invert the direction of all subtyping premises in it. The operation prems[T1<:T2]:𝚓𝚞𝚜𝚝T2<:T1{prems}[T_{1}<:T_{2}]:\;\mathtt{just}\;T_{2}<:T_{1} does just that. Notice that the body of a selector is an option. This is because it is common for some iteration to return no values (𝚗𝚘𝚝𝚑𝚒𝚗𝚐\mathtt{nothing}). The examples in Section 3 show this aspect. Since options are commonly used in the context of selector iterations, we have designed our selector operation to automatically handle them. That is, 𝚗𝚘𝚝𝚑𝚒𝚗𝚐\mathtt{nothing}s are automatically removed, and the selector above returns the list of new subtyping premises rather than a list of options. The selector e(𝚔𝚎𝚎𝚙)[p]:e{e(\mathtt{keep})}[p]:\;e works like an ordinary selector except that it also returns the elements that failed the pattern-matching.
Uniquefy: When transforming languages it is often necessary to assign distinct variables. The example of algorithmic subtyping in the introduction is archetypal. Tr\mathcal{L}\textendash\textsf{Tr} accommodates this operation as primitive with 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy}.
𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(e1,e2,str)(x,y):e3\mathtt{uniquefy}(e_{1},e_{2},str)\Rightarrow(x,y):e_{3} takes in input a list of formulae e1e_{1}, a map e2e_{2}, and a string strstr (we shall discuss xx, yy, and e3e_{3} shortly). This operation modifies the formulae e2e_{2} to use different variable names when a variable is mentioned more than once. However, not every variable is subject to the replacement. Only the variables that appear in some specific positions are targeted. The map e2e_{2} and the string strstr contain the information to identify these positions. e2e_{2} maps operator names and predicate names to a list that contains a label (as a string) for each of their arguments. For example, the map m={[in,in,out]}m=\{\vdash\;\mapsto[``{in}",``{in}",``{out}"]\} says that Γ\Gamma and ee are inputs in a formula Γe:T\Gamma\vdash e:T, and that TT is the output. Similarly, the map {[contravariant,covariant]}\{\to\;\mapsto[``{contravariant}",``{covariant}""]\} says that T1T_{1} is contravariant and T2T_{2} is covariant in T1T2T_{1}\to T_{2}. The string strstr specifies a label. Tr\mathcal{L}\textendash\textsf{Tr} inspects the formulae in e1e_{1} and their terms. Arguments that correspond to the label according to the map then receive a new variable. To make an example, if 𝑙𝑓\mathit{lf} is the list of premises of (t-app) and mm is defined as above (input-output modes), the operation 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(𝑙𝑓,m,out)(x,y):e3\mathtt{uniquefy}(\mathit{lf},m,``{out}")\Rightarrow(x,y):e_{3} creates the premises of (t-app’) shown in the introduction. Furthermore, the computation continues with the expression e3e_{3} in which xx is bound to these premises and yy is bound to a map that summarizes the changes made by 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy}. This latter map associates every variable XX to the list of new variables that 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} has used to replace XX. For example, since 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} created the premises of (t-app’) by replacing T1T_{1} in two different positions with T11T_{11} and T12T_{12}, the map {T1[T11,T12]}\{T_{1}\mapsto[T_{11},T_{12}]\} is passed to e3e_{3} as yy. Section 3 will show two examples that make use of 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy}.
Control Flow: Tr\mathcal{L}\textendash\textsf{Tr} includes the if-then-else statement with typical guards. Tr\mathcal{L}\textendash\textsf{Tr} also has the sequence operation ; (and 𝚜𝚔𝚒𝚙\mathtt{skip}) to execute language transformations one after another. e1;re2e_{1};_{\text{r}}e_{2}, instead, executes sequences of transformations on rules. After e1e_{1} evaluates to a rule, e2e_{2} makes use of that rule as the subject of its transformations.
Programming Rules, Premises, and Terms: In Tr\mathcal{L}\textendash\textsf{Tr} a programmer can write Tr\mathcal{L}\textendash\textsf{Tr} terms (t^\hat{t}), Tr\mathcal{L}\textendash\textsf{Tr} formulae (f^\hat{f}), and Tr\mathcal{L}\textendash\textsf{Tr} rules (r^\hat{r}) in expressions. These differ from the terms, formulae and rules of language definitions in that they can contain arbitrary expressions, such as if-then-else statements, at any position. This is a useful feature as it provides a declarative way to create rules, premises, or terms. To make an example with rule creation, we can write

\inferenceprems[T1<:T2]:𝚓𝚞𝚜𝚝T2<:T1f\inference{{prems}[T_{1}<:T_{2}]:\;\mathtt{just}\;T_{2}<:T_{1}}{f}

where prems is the list of premises from above, and ff is a formula. As we can see, using expressions above the horizontal line is a convinient way to compute the premises of a rule.
Other Operations: The operation 𝚏𝚘𝚕𝚍prednamee\mathtt{fold}\;predname\;e creates a list of formulae that interleaves prednamepredname to any two subsequent elements of the list ee. To make an example, the operation 𝚏𝚘𝚕𝚍=[T1,T2,T3,T4]\mathtt{fold}\;=\;[T_{1},T_{2},T_{3},T_{4}] generates the list of formulae [T1=T2,T2=T3,T3=T4][T_{1}=T_{2},T_{2}=T_{3},T_{3}=T_{4}]. 𝚟𝚊𝚛𝚜(e)\mathtt{vars}(e) returns the list of the meta-variables in ee. 𝚗𝚎𝚠𝚅𝚊𝚛\mathtt{newVar} returns a meta-variable that has not been previously used. The tick operator ee\texttt{'} gives a prime to the meta-variables of e1e_{1} (X{X} becomes X{X^{\prime}}). 𝚟𝚊𝚛𝚜\mathtt{vars} and the tick operator also work on lists of terms.
Variables and Substitution: Some variables have a special treatment in Tr\mathcal{L}\textendash\textsf{Tr}. We can refer to the value that a selector iterates over with the variable 𝑠𝑒𝑙𝑓\mathit{self}. If we are in a context that manipulates a rule, we can also refer to the premises and conclusion with variables 𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠\mathit{premises} and 𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛\mathit{conclusion}. We use the notation e[v/x]e[v/x] to denote the capture-avoiding substitution. θ\theta ranges over finite sequences of substitutions denoted with [v1/x1,,vn/xn][v_{1}/x_{1},\ldots,v_{n}/x_{n}]. e[v1/x2,v1/x2,,vn/xn]e[v_{1}/x_{2},v_{1}/x_{2},\ldots,v_{n}/x_{n}] means ((e[v1/x1])[v2/x2])[vn/xn]((e[v_{1}/x_{1}])[v_{2}/x_{2}])\ldots[v_{n}/x_{n}]. We omit the definition of substitution because it is standard, for the most part. The only aspect that differs from standard substitution is that we do not substitute 𝑠𝑒𝑙𝑓\mathit{self}, 𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠\mathit{premises} and 𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛\mathit{conclusion} in those contexts that will be set at run-time (;r;_{\textsf{r}}, and selector body). For example, (e1;re2)[v/𝒳](e1[v/𝒳]);re2(e_{1};_{\textsf{r}}e_{2})[v/\mathcal{X}]\equiv(e_{1}[v/\mathcal{X}]);_{\textsf{r}}e_{2}, where 𝒳{𝑠𝑒𝑙𝑓,𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠,𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛}\mathcal{X}\in\{\mathit{self},\mathit{premises},\mathit{conclusion}\}.

2.2 Operational Semantics of Tr\mathcal{L}\textendash\textsf{Tr}

Dynamic Semantics V;;eV;;eV;\mathcal{L};e\longrightarrow V;\mathcal{L};e

\inference{cnameX::=v}GV;(G,R);cname@V;(G,R);v\displaystyle\inference{\{cname\;{X}::=v\}\in G}{V;(G,R);cname\longrightarrow_{\mathtt{@}}V;(G,R);v} (r-cname-ok)
\inference{cnameX::=v}GV;(G,R);cname@V;(G,R);𝚎𝚛𝚛𝚘𝚛\displaystyle\inference{\{cname\;{X}::=v\}\not\in G}{V;(G,R);cname\longrightarrow_{\mathtt{@}}V;(G,R);\mathtt{error}} (r-cname-fail)
V;(G,R);𝚐𝚎𝚝𝚁𝚞𝚕𝚎𝚜@V;(G,R);R\displaystyle V;(G,R);\mathtt{getRules}\longrightarrow_{\mathtt{@}}V;(G,R);R (r-getRules)
V;(G,R);𝚜𝚎𝚝𝚁𝚞𝚕𝚎𝚜v@V;(G,v);𝚜𝚔𝚒𝚙\displaystyle V;(G,R);\mathtt{setRules}\;v\longrightarrow_{\mathtt{@}}V;(G,v);\mathtt{skip} (r-setRules)
\inferenceG=(G\cname){cnameX::=v}V;(G,R);(cnameX::=v)@;(G,R);𝚜𝚔𝚒𝚙\displaystyle\inference{G^{\prime}=(G\backslash cname)\cup\{cname\;{X}::=v\}}{V;(G,R);(cname\;{X}::=v)\longrightarrow_{\mathtt{@}}\emptyset;(G^{\prime},R);\mathtt{skip}} (r-new-syntax)
\inference{cnameX::=v}GV;(G,R);(cnameX::=v)@;(G,R);cnameX::=v@v\displaystyle\inference{\{cname\;{X}::=v^{\prime}\}\in G}{V;(G,R);(cname\;{X}::=\ldots\;v)\longrightarrow_{\mathtt{@}}\emptyset;(G,R);cname\;{X}::=v^{\prime}@v} (r-add-syntax-ok)
\inference{cnameX::=v}GV;(G,R);(cnameX::=v)@;(G,R);𝚎𝚛𝚛𝚘𝚛\displaystyle\inference{\{cname\;{X}::=v^{\prime}\}\not\in G}{V;(G,R);(cname\;{X}::=\ldots\;v)\longrightarrow_{\mathtt{@}}\emptyset;(G^{\prime},R);\mathtt{error}} (r-add-syntax-fail)
V;;(𝚜𝚔𝚒𝚙;e)@V;;e\displaystyle V;\mathcal{L};(\mathtt{skip};e)\longrightarrow_{\mathtt{@}}V;\mathcal{L};e (r-seq)
V;;v;re@V;;eθrule(v)\displaystyle V;\mathcal{L};v;_{\textsf{r}}e\longrightarrow_{\mathtt{@}}V;\mathcal{L};e\theta_{\textsf{rule}}^{(v)} (r-rule-comp)
V;;𝚗𝚒𝚕[p]:e@V;;𝚗𝚒𝚕\displaystyle V;\mathcal{L};{\mathtt{nil}}[p]:\;e\longrightarrow_{\mathtt{@}}V;\mathcal{L};\mathtt{nil} (r-selector-nil)
\inference𝑚𝑎𝑡𝑐ℎ(v1,p)=θθ={θrule(r)if v1=r{𝑠𝑒𝑙𝑓v1}otherwiseV;;(𝚌𝚘𝚗𝚜v1v2)[p]:e@V;;(𝑐𝑜𝑛𝑠eθθ(v2[p]:e))\displaystyle\inference{\mathit{match}(v_{1},p)=\theta\qquad\theta^{\prime}=\mbox{$\begin{cases}\theta_{\textsf{rule}}^{(r)}&\mbox{if }v_{1}=r\\ \{\mathit{self}\mapsto v_{1}\}&\mbox{otherwise}\end{cases}$}}{V;\mathcal{L};{(\mathtt{cons}\;v_{1}\;v_{2})}[p]:\;e\longrightarrow_{\mathtt{@}}V;\mathcal{L};(\mathit{cons}^{*}\;{e\theta\theta^{\prime}}\;{({v_{2}}[p]:\;e)})} (r-selector-cons-ok)
\inference𝑚𝑎𝑡𝑐ℎ(v1,p)θV;;(𝚌𝚘𝚗𝚜v1v2)[p]:e@V;;(v2[p]:e)\displaystyle\inference{\mathit{match}(v_{1},p)\not=\theta}{V;\mathcal{L};{(\mathtt{cons}\;v_{1}\;v_{2})}[p]:\;e\longrightarrow_{\mathtt{@}}V;\mathcal{L};({v_{2}}[p]:\;e)} (r-selector-cons-fail)
\inferenceXV𝑣𝑎𝑟𝑠()𝑟𝑎𝑛𝑔𝑒(𝑡𝑖𝑐𝑘)V;(G,R);𝚗𝚎𝚠𝚅𝚊𝚛@V{X};;X\displaystyle\inference{{X^{\prime}}\not\in V\cup\mathit{vars}(\mathcal{L})\cup\mathit{range}(\mathit{tick})}{V;(G,R);\mathtt{newVar}\;\longrightarrow_{\mathtt{@}}V\cup\{{X^{\prime}}\};\mathcal{L};{X^{\prime}}} (r-newvar)
\inference(𝑙𝑓,v2)=𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦lf(𝑙𝑓,v1,str,𝚖𝚊𝚙([],[]))V;;𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(𝑙𝑓,v1,str)(x,y):e@V;;e[𝑙𝑓/x,v2/y]\displaystyle\inference{(\mathit{lf}^{\prime},v_{2})=\mathit{uniquefy}_{\textsf{lf}}(\mathit{lf},v_{1},str,\mathtt{map}([],[]))}{V;\mathcal{L};\mathtt{uniquefy}(\mathit{lf},v_{1},str)\Rightarrow(x,y):e\longrightarrow_{\mathtt{@}}V;\mathcal{L};e[\mathit{lf}^{\prime}/x,v_{2}/y]} (r-uniquefy-ok)
\inference𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦lf(𝑙𝑓,v1,str,𝚖𝚊𝚙([],[]))=𝑓𝑎𝑖𝑙V;;𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(𝑙𝑓,v1,str)(x,y):e@V;;𝚎𝚛𝚛𝚘𝚛\displaystyle\inference{\mathit{uniquefy}_{\textsf{lf}}(\mathit{lf},v_{1},str,\mathtt{map}([],[]))=\mathit{fail}}{V;\mathcal{L};\mathtt{uniquefy}(\mathit{lf},v_{1},str)\Rightarrow(x,y):e\longrightarrow_{\mathtt{@}}V;\mathcal{L};\mathtt{error}} (r-uniquefy-fail)
where θrule(r)[r/𝑠𝑒𝑙𝑓,v1/𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠,v2/𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛]if r=\inferencev1v2\displaystyle\text{where }\theta_{\textsf{rule}}^{(r)}\equiv[r/\mathit{self},v_{1}/\mathit{premises},v_{2}/\mathit{conclusion}]\qquad\text{if }r=\inference{v_{1}}{v_{2}}
Figure 1: Reduction Semantics of Tr\mathcal{L}\textendash\textsf{Tr}

In this section we show a small-step operational semantics for Tr\mathcal{L}\textendash\textsf{Tr}. A configuration is denoted with V;;eV;\mathcal{L};e, where ee is an expression, \mathcal{L} is the language subject of the transformation, and VV is the set of meta-variables that have been generated by 𝚗𝚎𝚠𝚅𝚊𝚛\mathtt{newVar}. Calls to 𝚗𝚎𝚠𝚅𝚊𝚛\mathtt{newVar} make sure not to produce name clashes.

The main reduction relation is V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}, defined as follows. Evaluation contexts EE are straightforward and can be found in Appendix LABEL:evaluationcontexts.

This relation relies on a step V;;e@V;;eV;\mathcal{L};e\longrightarrow_{\mathtt{@}}V^{\prime};\mathcal{L}^{\prime};e^{\prime}, which concretely performs the step. Since a transformation may insert ill-formed elements such as TT\vdash T\;T or ee\to\;e\;e in the language, we also rely on a notion of type checking for language definitions \vdash\mathcal{L}^{\prime} decided by the language designer. For example, our implementation of Tr\mathcal{L}\textendash\textsf{Tr} compiles languages to λ\lambda-prolog and detects ill-formed languages at each step, but the logic of Coq, Agda, Isabelle could be used as well. Our type soundness theorem works regardless of the definition of \vdash\mathcal{L}^{\prime}.

Fig. 1 shows the reduction relation V;;e@V;;eV;\mathcal{L};e\longrightarrow_{\mathtt{@}}V^{\prime};\mathcal{L}^{\prime};e^{\prime}. We show the most relevant rules. The rest of the rules can be found in Appendix LABEL:app:operational. (r-cname-ok) and (r-cname-fail) handle the encounter of a category name. We retrieve the corresponding list of terms from the grammar or throw an error if the production does not exist. (r-getRules) retrieves the list of rules of the current language, and (r-setRules) updates this list. (r-new-syntax) replaces the grammar with a new one that contains the new production. The meta-operation G\cnameG\backslash cname in that rule removes the production with category name cnamecname from GG (definition is straightforward and omitted). The position of cnamecname in (cnameX::=v)(cname\;{X}::=v) is not an evaluation context, therefore (r-cname-ok) will not replace that name. (r-add-syntax-ok) takes a step to the instruction for adding new syntax. The production to be added includes both old and new grammar terms. (r-add-syntax-fail) throws an error when the category name does not exist in the grammar, or the meta-variable does not match. (r-rule-seq) applies when the first expression has evaluated, and starts the evaluation of the second expression. (Evaluation context E;eE;e evaluates the first expression) (r-rule-comp) applies when the first expression has evaluated to a rule, and starts the evaluation of the second expression where θrule(v)\theta_{\textsf{rule}}^{(v)} sets this rule as the current rule. Rules (r-selector-*) define the behavior of a selector. (r-selector-cons-ok) and (r-selector-cons-fail) make use of the meta-operation 𝑚𝑎𝑡𝑐ℎ(v1,p)=θ\mathit{match}(v_{1},p)=\theta. If this operation succeeds it returns the substitutions θ\theta with the associations computed during pattern-matching. The definition of 𝑚𝑎𝑡𝑐ℎ\mathit{match} is standard and is omitted. The body is evaluated with these substitutions and with 𝑠𝑒𝑙𝑓\mathit{self} instantiated with the element selected. If the element selected is a rule, then the body is instantiated with θrule(v)\theta_{\textsf{rule}}^{(v)} to refer to that rule as the current rule. The body of the selector always returns an option type. However, 𝑐𝑜𝑛𝑠\mathit{cons}^{*} is defined as: 𝑐𝑜𝑛𝑠e1e2𝚒𝚏(𝚒𝚜𝙽𝚘𝚝𝚑𝚒𝚗𝚐e1)𝚝𝚑𝚎𝚗e2𝚎𝚕𝚜𝚎𝚌𝚘𝚗𝚜(𝚐𝚎𝚝e1)e2\mathit{cons}^{*}\;{e_{1}}\;{e_{2}}\equiv\mathtt{if}\;(\mathtt{isNothing}\;e_{1})\;\mathtt{then}\;e_{2}\;\mathtt{else}\;\mathtt{cons}\;(\mathtt{get}\;e_{1})\;e_{2}. Therefore, 𝚗𝚘𝚝𝚑𝚒𝚗𝚐\mathtt{nothing}s are discarded, and values wrapped in 𝚓𝚞𝚜𝚝\mathtt{just}s are unwrapped. (r-newvar) returns a new meta-variable and augments VV with it. Meta-variables are chosen among those that are not in the language, have not previously been generated by 𝚗𝚎𝚠𝚅𝚊𝚛\mathtt{newVar}, and are not in the range of 𝑡𝑖𝑐𝑘\mathit{tick}. This meta-operation is used by the tick operator to give a prime to meta-variables. r-newvar avoids clashes with these variables, too. (r-uniquefy-ok) and (r-uniquefy-fail) define the semantics for 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy}. They rely on the meta-operation 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦r(𝑙𝑓,v,str,𝚖𝚊𝚙([],[]))\mathit{uniquefy}_{\textsf{r}}(\mathit{lf},v,str,\mathtt{map}([],[])), which takes the list of formulae 𝑙𝑓\mathit{lf}, the map vv, the string strstr, and an empty map to start computing the result map. The definition of 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦r\mathit{uniquefy}_{\textsf{r}} is mostly a recursive traversal of list of formuale and terms, and we omit that. It can be found in Appendix LABEL:uniquefy. This function can succeed and return a pair (𝑙𝑓,v2)(\mathit{lf}^{\prime},v_{2}) where 𝑙𝑓\mathit{lf}^{\prime} is the modified list of formulae and v2v_{2} maps meta-variables to the new meta-variables that have replaced it. 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦r\mathit{uniquefy}_{\textsf{r}} can also fail. This may happen when, for example, a map such as {contra}\{\to\;\mapsto``contra"\} is passed when \to requires two arguments.

2.3 Type System of Tr\mathcal{L}\textendash\textsf{Tr}

Type System (Configurations) ΓV;;e\Gamma\vdash\;V;\mathcal{L};e

Type System (Expressions) Γe:T\Gamma\vdash\;e:T

(t-var)       Γ,x:Tx:T\displaystyle\displaystyle\Gamma,x:T\vdash\;x:T    (t-opname)   Γe:ListTerm   Γ(opnamee):Term \displaystyle\displaystyle{\hbox{\hskip 33.19014pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:{\mathtt{List}}\;\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 43.36288pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;(opname\;e):\mathtt{Term}$}}}}}}    (t-opname-var)   Γe:ListTerm   Γ,x:OpName(xe):Term \displaystyle\displaystyle{\hbox{\hskip 33.19014pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:{\mathtt{List}}\;\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 49.5817pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma,x:\mathtt{OpName}\vdash\;(x\;e):\mathtt{Term}$}}}}}}
(t-meta-var)    X:𝚃𝚎𝚛𝚖\displaystyle\displaystyle{X}:\mathtt{Term}     (t-abs)   Γe:Term   Γ(z)e:Term \displaystyle\displaystyle{\hbox{\hskip 22.45554pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 28.3982pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;({z})e:\mathtt{Term}$}}}}}}     (t-subs)   Γe1:TermΓe2:Term   Γe1[/e2z]:Term \displaystyle\displaystyle{\hbox{\hskip 51.31654pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:\mathtt{Term}\quad\Gamma\vdash\;e_{2}:\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 36.18301pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}[e_{2}/{z}]:\mathtt{Term}$}}}}}}
(t-predname)   Γe:ListTerm   Γ(prednamee):Formula \displaystyle\displaystyle{\hbox{\hskip 33.19014pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:{\mathtt{List}}\;\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 54.97319pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;(predname\;e):\mathtt{Formula}$}}}}}}     (t-predname-var)   Γe:ListTerm   Γ,x:PredName(xe):Formula \displaystyle\displaystyle{\hbox{\hskip 33.19014pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:{\mathtt{List}}\;\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 61.39406pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma,x:\mathtt{PredName}\vdash\;(x\;e):\mathtt{Formula}$}}}}}}
(t-rule)   Γe1:ListFormula Γe2:Formula   Γ\inferencee1e2:Rule \displaystyle\displaystyle{\hbox{\hskip 41.16782pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:{\mathtt{List}}\;\mathtt{Formula}$}}}\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{2}:\mathtt{Formula}$}}}\vbox{}}}}\over\hbox{\hskip 31.84348pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;\inference{e_{1}}{e_{2}}:\mathtt{Rule}$}}}}}}     (t-seq)   Γe1:Language Γe2:Language   Γe1;e2:Language \displaystyle\displaystyle{\hbox{\hskip 32.79568pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:\mathtt{Language}$}}}\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{2}:\mathtt{Language}$}}}\vbox{}}}}\over\hbox{\hskip 39.17636pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1};e_{2}:\mathtt{Language}$}}}}}}     (t-rule-comp)   Γe1:Rule Γ,Γrulee2:Rule   Γe1;re2:Rule \displaystyle\displaystyle{\hbox{\hskip 31.59924pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:\mathtt{Rule}$}}}\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma,\Gamma_{\textsf{rule}}\vdash\;e_{2}:\mathtt{Rule}$}}}\vbox{}}}}\over\hbox{\hskip 30.28189pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1};_{\textsf{r}}e_{2}:\mathtt{Rule}$}}}}}}
(t-selector)   Γe1:ListTΓp:TΓ   =Γ′′{Γrule=if TRule:selfTotherwise   Γ,Γ,Γ′′e2:OptionT   Γe1[p]:e2:ListT \displaystyle\displaystyle{\hbox{\hskip 168.72173pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:{\mathtt{List}}\;T\quad\Gamma\vdash\;p:T\Rightarrow\Gamma^{\prime}$}\hskip 18.49988pt\hbox{\hbox{$\displaystyle\displaystyle\Gamma^{\prime\prime}=\mbox{$\displaystyle\begin{cases}\Gamma_{\textsf{rule}}&\mbox{if }T=\mathtt{Rule}\\ \mathit{self}:T&\mbox{otherwise}\end{cases}$}$}\hskip 18.49988pt\hbox{\hbox{$\displaystyle\displaystyle\Gamma,\Gamma^{\prime},\Gamma^{\prime\prime}\vdash\;e_{2}:\mathtt{Option}\;T^{\prime}$}}}}}\vbox{}}}\over\hbox{\hskip 42.4434pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;{e_{1}}[p]:\;e_{2}:{\mathtt{List}}\;T^{\prime}$}}}}}}
(t-syntax-new and t-syntax-add)   Γe:ListTerm   ΓcnameX::=e:LanguageΓcnameX::=e:Language \displaystyle\displaystyle{\hbox{\hskip 33.19014pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:{\mathtt{List}}\;\mathtt{Term}$}}}\vbox{}}}\over\hbox{\hskip 65.62836pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\mbox{$\displaystyle\begin{array}[]{c}\Gamma\vdash\;cname\;{X}::=e:\mathtt{Language}\\ \Gamma\vdash\;cname\;{X}::=\ldots\;e:\mathtt{Language}\end{array}$ }$}}}}}}     (t-cname)        Γcname:𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖\displaystyle\displaystyle\Gamma\vdash cname:{\mathtt{List}}\;\mathtt{Term}
(t-getRules)        Γ𝚐𝚎𝚝𝚁𝚞𝚕𝚎𝚜:𝙻𝚒𝚜𝚝𝚁𝚞𝚕𝚎\displaystyle\displaystyle\Gamma\vdash\mathtt{getRules}:{\mathtt{List}}\;\mathtt{Rule}     (t-setRules)   Γe:ListRule   ΓsetRulese:Language \displaystyle\displaystyle{\hbox{\hskip 33.19014pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e:{\mathtt{List}}\;\mathtt{Rule}$}}}\vbox{}}}\over\hbox{\hskip 52.08992pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;\mathtt{setRules}\;e:\mathtt{Language}$}}}}}}
(t-uniquefy)               Γe1:ListFormula Γe2:MapT(ListString)T=OpName or T=PredName Γ,x:ListFormula,y:MapTerm(ListTerm)e3:T           Γuniquefy(e1,e2,str)(x,y):e3:T (t-skip)       Γskip:Language (t-newvar)      ΓnewVar:Term \displaystyle{\vbox{\hbox{\hbox{\small\small{(t-uniquefy)}}}\hbox{$\displaystyle\displaystyle{\hbox{\hskip 118.3857pt\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{1}:{\mathtt{List}}\;\mathtt{Formula}$}}}\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;e_{2}:\mathtt{Map}\;T^{\prime}\;({\mathtt{List}}\;\mathtt{String})\quad T^{\prime}=\mathtt{OpName}\text{ or }T^{\prime}=\mathtt{PredName}$}}}\vbox{\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma,x:{\mathtt{List}}\;\mathtt{Formula},y:\mathtt{Map}\;\mathtt{Term}\;({\mathtt{List}}\;\mathtt{Term})\vdash\;e_{3}:T$}}}\vbox{}}}}}\over\hbox{\hskip 81.58128pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;\mathtt{uniquefy}(e_{1},e_{2},str)\Rightarrow(x,y):e_{3}:T$}}}}}}$}}}\qquad\begin{array}[]{c}{\vbox{\hbox{\hbox{\small\small{(t-skip)}}}\hbox{$\displaystyle\vbox{\hbox{\hskip 37.92044pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;\mathtt{skip}:\mathtt{Language}$}}}}}}$}}}\\[7.74997pt] {\vbox{\hbox{\hbox{\small\small{(t-newvar)}}}\hbox{$\displaystyle\vbox{\hbox{\hskip 33.1955pt\vbox{\vbox{}\hbox{\thinspace\hbox{\hbox{$\displaystyle\displaystyle\Gamma\vdash\;\mathtt{newVar}:\mathtt{Term}$}}}}}}$}}}\end{array}
where Γrule𝑠𝑒𝑙𝑓:𝚁𝚞𝚕𝚎,𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊,𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛:𝙵𝚘𝚛𝚖𝚞𝚕𝚊\displaystyle\text{where }\Gamma_{\textsf{rule}}\equiv\mathit{self}:\mathtt{Rule},\mathit{premises}:{\mathtt{List}}\;\mathtt{Formula},\mathit{conclusion}:\mathtt{Formula}
Figure 2: Type System of Tr\mathcal{L}\textendash\textsf{Tr}

In this section we define a type system for Tr\mathcal{L}\textendash\textsf{Tr}. Types are defined as follows

TypeT::=𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎𝚁𝚞𝚕𝚎𝙵𝚘𝚛𝚖𝚞𝚕𝚊𝚃𝚎𝚛𝚖𝙻𝚒𝚜𝚝T𝙼𝚊𝚙TT𝙾𝚙𝚝𝚒𝚘𝚗T𝚂𝚝𝚛𝚒𝚗𝚐𝙾𝚙𝙽𝚊𝚖𝚎𝙿𝚛𝚎𝚍𝙽𝚊𝚖𝚎Type EnvΓ::=Γ,x:T\begin{array}[]{l@{\;\;}lcl}\text{\sf Type}&T&::=&\mathtt{Language}\mid\mathtt{Rule}\mid\mathtt{Formula}\mid\mathtt{Term}\\ &&&{\mathtt{List}}\;T\mid\mathtt{Map}\;T\;T\mid\mathtt{Option}\;T\mid\mathtt{String}\mid\mathtt{OpName}\mid\mathtt{PredName}\\ \text{\sf Type Env}&\Gamma&::=&\emptyset\mid\Gamma,x:T\end{array}

We have a typical type environment that maps variables to types. Fig. 2.3 shows the type system. The typing judgement V;;e\vdash V;\mathcal{L};e means that the configuration V;;eV;\mathcal{L};e is well-typed. This judgment checks that the variables of VV and those in \mathcal{L} are disjoint. This is an invariant that ensures that 𝚗𝚎𝚠𝚅𝚊𝚛\mathtt{newVar} always produces fresh names. We also check that \mathcal{L} is well-typed and that ee is of type 𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\mathtt{Language}.

We type check expressions with the typing judgement Γe:T\Gamma\vdash\;e:T, which means that ee has type TT under the assignments in Γ\Gamma. Most typing rules are straightforward. We omit rules about lists and maps because they are standard. We comment only on the rules that are more involved. (t-selector) type checks a selector operation. We use Γp:TΓ\Gamma\vdash\;p:T\Rightarrow\Gamma^{\prime} to type check the pattern pp and return the type environment for the variables of the pattern. Its definition is standard and is omitted. When we type check the body e2e_{2} we then include Γ\Gamma^{\prime}. If the elements of the list are rules then we also include Γrule\Gamma_{\textsf{rule}} to give a type to the variables for referring to the current rule. Otherwise, we assign 𝑠𝑒𝑙𝑓\mathit{self} the type of the element of the list. Selectors with 𝚔𝚎𝚎𝚙\mathtt{keep} are analogous and omitted. (t-rule-comp) type checks a rule composition. In doing so, we type check the second expression with Γrule\Gamma_{\textsf{rule}}. (t-uniquefy) type checks the 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} operation. As we rename variables depending on the position they hold in terms and formulae, the keys of the map are of type 𝙾𝚙𝙽𝚊𝚖𝚎\mathtt{OpName} or 𝙿𝚛𝚎𝚍𝙽𝚊𝚖𝚎\mathtt{PredName}, and values are strings. We type check e3e_{3} giving xx the type of list of formulae, and yy the type of a map from meta-variables to list of meta-variables.

We have proved that Tr\mathcal{L}\textendash\textsf{Tr} is type sound.

Theorem 2.1 (Type Soundness)

For all Γ\Gamma, VV, \mathcal{L}, ee, if V;;e\vdash V;\mathcal{L};e then V;;eV;;eV;\mathcal{L};e\longrightarrow^{*}V^{\prime};\mathcal{L}^{\prime};e^{\prime} s.t. i) e=𝚜𝚔𝚒𝚙e^{\prime}=\mathtt{skip}, ii) e=𝚎𝚛𝚛𝚘𝚛e^{\prime}=\mathtt{error}, or iii) V;;eV′′;′′;e′′V^{\prime};\mathcal{L}^{\prime};e^{\prime}\longrightarrow V^{\prime\prime};\mathcal{L}^{\prime\prime};e^{\prime\prime}, for some e′′e^{\prime\prime}.

The proof is by induction on the derivation V;;e\vdash V;\mathcal{L};e, and follows the standard approach of Wright and Felleisen [WrightFelleisen94] through a progress theorem and a subject reduction theorem. The proof can be found in Appendix 0.D.

3 Examples

We show the applicability of Tr\mathcal{L}\textendash\textsf{Tr} with two examples of language transformations: adding subytyping [tapl] and switching to big-step semantics [Kahn87]. In the code we use let-binding, pattern-matching, and an overlap operation that returns true if two terms have variables in common. These operations can be easily defined in Tr\mathcal{L}\textendash\textsf{Tr}, and we show them in Appendix 0.E. The code below defines the transformation for adding subtyping. We assume that two maps are already defined, mode={[inp,inp,out]}mode=\{\vdash\;\mapsto[``inp",\;``inp",\;``out"]\} and variance={[contra,cova]}variance=\{\to\;\;\mapsto[``contra",\;``cova"]\}.

1𝚜𝚎𝚝𝚁𝚞𝚕𝚎𝚜\mathtt{setRules}
2 𝚐𝚎𝚝𝚁𝚞𝚕𝚎𝚜(𝚔𝚎𝚎𝚙)[([Γ,e,T])]:\mathtt{getRules}(\mathtt{keep})[(\vdash[\Gamma,e,T])]:
3 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(premises,mode,out)=>(uniq,newpremises):\mathtt{uniquefy}(premises,mode,``out")=>(uniq,newpremises):
4 newpremises@𝚌𝚘𝚗𝚌𝚊𝚝(𝚖𝚊𝚙𝙺𝚎𝚢𝚜(uniq)[Tf]:𝚏𝚘𝚕𝚍<:uniq(Tf))¯\underline{newpremises\;@\;\mathtt{concat}(\mathtt{mapKeys}(uniq)[T_{f}]:\mathtt{fold}<:uniq(T_{f}))}
5 conclusionconclusion
6 ;r;_{\textsf{r}}
7 𝚌𝚘𝚗𝚌𝚊𝚝(premises(𝚔𝚎𝚎𝚙)[T1<:T2]:\mathtt{concat}(premises(\mathtt{keep})[T_{1}<:T_{2}]:
8 premises[([Γ,ev,(cvTsv)])]:premises[(\vdash[\Gamma,e_{v},(c_{v}\;Ts_{v})])]:
9 𝚕𝚎𝚝vmap=𝚖𝚊𝚙(Tsv,variance(cv))𝚒𝚗\mathtt{let}\;vmap=\mathtt{map}(Ts_{v},\;variance(c_{v}))\;\mathtt{in}\;
10 𝚒𝚏vmap(T1)=contra𝚝𝚑𝚎𝚗T2<:T1\mathtt{if}\;vmap(T_{1})=``contra"\;\mathtt{then}\;T_{2}<:T_{1}
11 𝚎𝚕𝚜𝚎𝚒𝚏vmap(T1)=inv𝚊𝚗𝚍vmap(T2)=inv𝚝𝚑𝚎𝚗T1=T2𝚎𝚕𝚜𝚎T1<:T2)¯\underline{\mathtt{else}\;\mathtt{if}\;vmap(T_{1})=``inv"\;\mathtt{and}\;vmap(T_{2})=``inv"\;\mathtt{then}\;T_{1}=T_{2}\;\mathtt{else}\;T_{1}<:T_{2})} conclusionconclusion
12 ;r;_{\textsf{r}}
13 𝚕𝚎𝚝𝑜𝑢𝑡𝑝𝑢𝑡𝑉𝑎𝑟𝑠=𝚖𝚊𝚝𝚌𝚑conclusion𝚠𝚒𝚝𝚑([Γ,ec,Tc])𝚟𝚊𝚛𝚜(Tc)𝚒𝚗\mathtt{let}\;\mathit{outputVars}=\mathtt{match}\;conclusion\;\mathtt{with}\;(\vdash[\Gamma,e_{c},T_{c}])\Rightarrow\mathtt{vars}(T_{c})\;\mathtt{in}\;
14 𝚕𝚎𝚝joins=𝚖𝚊𝚙𝙺𝚎𝚢𝚜(uniq)[Ti]:\mathtt{let}\;joins=\mathtt{mapKeys}(uniq)[T_{i}]:
15 𝚒𝚏Ti𝚒𝚗𝑜𝑢𝑡𝑝𝑢𝑡𝑉𝑎𝑟𝑠𝚝𝚑𝚎𝚗(uniq(Ti)=Ti)𝚎𝚕𝚜𝚎𝚗𝚘𝚝𝚑𝚒𝚗𝚐\mathtt{if}\;T_{i}\;\mathtt{in}\;\mathit{outputVars}\;\mathtt{then}\;(\sqcup\;uniq(T_{i})\;=\;T_{i})\;\mathtt{else}\;\mathtt{nothing}
16 𝚒𝚗premises@joins¯\mathtt{in}\;\underline{premises\;@\;joins}
17 conclusionconclusion

Line 1 updates the rules of the language with the rules computed by the code in lines 2-17. Line 2 selects all typing rules, and each of them will be the subject of the transformations in lines 3-17. Line 3 calls 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} on the premises of the selected rule. We instruct 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} to give new variables to the outputs of the typing relation \vdash, if they are used more than once in that position. As previously described, 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} returns the list of new premises, which we bind to 𝑛𝑒𝑤𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠\mathit{newpremises}, and the map that assigns variables to the list of the new variables generated to replace them, which we bind to uniquniq. The body of 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} goes from line 4 to 17. Lines 4 and 5 build a new rule with the conclusion of the selected rule (line 5). It does so using the special variable name conclusion. The premises of this rule include the premises just generated by 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy}. Furthermore, we add premises computed as follows. With 𝚖𝚊𝚙𝙺𝚎𝚢𝚜(uniq)[Tf]\mathtt{mapKeys}(uniq)[T_{f}], we iterate over all the variables replaced by 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy}. We take the variables that replaced them and use fold to relate them all with subtyping. In other words, for each {T[T1,,Tn]}\{T\;\mapsto[T_{1},\ldots,T_{n}]\} in uniquniq, we have the formulae T1<:T2,,Tn1<:TnT_{1}<:T_{2},\ldots,T_{n-1}<:T_{n}. This transformation has created a rule with unique outputs and subtyping, but subtyping may be incorrect because if some variable is contravariant its corresponding subtyping premise should be swapped. Lines 7-11, then, adjust the subtyping premises based on the variance of types. Line 7 selects all subtyping premises of the form T1<:T2T_{1}<:T_{2}. For each, Line 8 selects typing premises with output of the form (cvTsv)(c_{v}\;Ts_{v}). We do so to understand the variance of variables. If the first argument of cvc_{v} is contravariant, for example, then the first element of TsvTs_{v} warrants a swap in a subtyping premise because it is used in the contravariant position. We achieve this by creating a map that associates the variance to each argument of cvc_{v}. The information about the variance for cvc_{v} is in variancevariance. If T1T_{1} or T2T_{2} (from the pattern of the selected premise) appear in TsvTs_{v} then they find themselves with a variance assigned in vmapvmap. Lines 10-11 generate a new premise based on the variance of variables. For example, if T1T_{1} is contravariant then we generate T2<:T1T_{2}<:T_{1}.

The program written so far (lines 1-11) is enough to add subtyping to several typing rules. For example, (t-app) can be transformed into (t-app’) with this program. However, some typing rules need a more sophisticated algorithm. Below is the typing rule for if-then-else on the left, and its version with subtyping on the right, which makes use of the join operator (\sqcup) (see, [tapl]).

\inferenceΓe1:𝙱𝚘𝚘𝚕Γe2:TΓe2:TΓ(𝑖𝑓e1e2e3):T\inferenceΓe1:𝙱𝚘𝚘𝚕Γe2:T1Γe3:T2T1T2=TΓ(𝑖𝑓e1e2e3):T{\footnotesize\begin{array}[]{ccc}{\inference{\Gamma\vdash\;e_{1}:\mathtt{Bool}\\ \Gamma\vdash\;e_{2}:T\quad\Gamma\vdash\;e_{2}:T}{\Gamma\vdash\;(\mathit{if}\;e_{1}\;e_{2}\;e_{3}):T}}&~~\Longrightarrow&{\inference{\Gamma\vdash e_{1}:\mathtt{Bool}\qquad\Gamma\vdash e_{2}:T_{1}\\ \\ \Gamma\vdash e_{3}:T_{2}\qquad T_{1}\sqcup T_{2}=T}{\Gamma\vdash(\mathit{if}\;e_{1}\;e_{2}\;e_{3}):T}}\end{array}}

If we removed T1T2T_{1}\sqcup T_{2} the meta-variable TT would have no precise instantiation because its counterpart variables have been given new names. Lines 13-17 accommodate for cases of the like. Line 13 saves the variables that appear the output type of the rule in outputVar. We then iterate over all the keys of uniquniq, that is, the variables that have been replaced. For each of them, we see if they appear in outputVar. If so then we create a join operator with the variables newly generated to replace this variable, which can be retrieved from uniquniq. We set the output of the join operator to be the variable itself, because that is the one used in the conclusion.

The algorithm above shows that 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} is a powerful operation of Tr\mathcal{L}\textendash\textsf{Tr}. To illustrate 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} further, let us consider a small example before we address big-step semantics. Suppose that we would like to make every test of equality explicit. We therefore want to disallow terms such as (𝚘𝚙eee)(\mathtt{op}\;e\;e\;e) to appear in the premises, and want to turn them into (𝚘𝚙e1e2e3)(\mathtt{op}\;e_{1}\;e_{2}\;e_{3}) together with premises e1=e2e_{1}=e_{2} and e2=e3e_{2}=e_{3}. In Tr\mathcal{L}\textendash\textsf{Tr} we can do this in the following way. Below we assume that the map allOps maps each operator to the string “yes” for each of its arguments. This instructs 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢\mathtt{uniquefy} to look for every argument.

1...
2 𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(premises,allOps,yes)=>(uniq,newpremises):\mathtt{uniquefy}(premises,allOps,``yes")=>(uniq,newpremises):
3 newpremises@𝚌𝚘𝚗𝚌𝚊𝚝(𝚖𝚊𝚙𝙺𝚎𝚢𝚜(uniq)[Tf]:𝚏𝚘𝚕𝚍=uniq(Tf)){newpremises\;@\;\mathtt{concat}(\mathtt{mapKeys}(uniq)[T_{f}]:\mathtt{fold}=uniq(T_{f}))}

Below, we show the code to turn language definitions into big-step semantics.

1𝚜𝚎𝚝𝚁𝚞𝚕𝚎𝚜\mathtt{setRules}
2 𝑉𝑎𝑙𝑢𝑒[v]:vv@\mathit{Value}[v]:v\longrightarrow v\;@
3 𝚐𝚎𝚝𝚁𝚞𝚕𝚎𝚜(𝚔𝚎𝚎𝚙)[(opes)et]:\mathtt{getRules}(\mathtt{keep})[(op\;es)\;\longrightarrow\;et]:
4 𝚒𝚏𝚒𝚜𝙴𝚖𝚙𝚝𝚢(Expression[(op_)]:self)𝚝𝚑𝚎𝚗𝚗𝚘𝚝𝚑𝚒𝚗𝚐𝚎𝚕𝚜𝚎\mathtt{if}\;\mathtt{isEmpty}({Expression}[(op\;\_)]:\;self)\;\mathtt{then}\;\mathtt{nothing}\;\mathtt{else}\;
5 𝚕𝚎𝚝vres=𝚗𝚎𝚠𝚅𝚊𝚛𝚒𝚗\mathtt{let}\;v_{res}=\mathtt{newVar}\;\mathtt{in}
6 𝚕𝚎𝚝emap=𝚌𝚛𝚎𝚊𝚝𝚎𝙼𝚊𝚙((es[e]:𝚗𝚎𝚠𝚅𝚊𝚛),es)𝚒𝚗\mathtt{let}\;emap=\mathtt{createMap}(({es}[e]:\;\mathtt{newVar}),es)\;\mathtt{in}
7 (𝚖𝚊𝚙𝙺𝚎𝚢𝚜(emap)[e]:𝚒𝚏𝚒𝚜𝚅𝚊𝚛(emap(e))𝚊𝚗𝚍𝚗𝚘𝚝(emap(e)𝚒𝚗𝚟𝚊𝚛𝚜(et))(\mathtt{mapKeys}(emap)[e]:\mathtt{if}\;\mathtt{isVar}(emap(e))\;\mathtt{and}\;\mathtt{not}(emap(e)\;\mathtt{in}\;\mathtt{vars}(et))
8 𝚝𝚑𝚎𝚗𝚗𝚘𝚝𝚑𝚒𝚗𝚐𝚎𝚕𝚜𝚎eemap(e))\mathtt{then}\;\mathtt{nothing}\;\mathtt{else}\;e\longrightarrow emap(e))
9 @(𝚒𝚏𝚗𝚘𝚝(et𝚒𝚗es)𝚝𝚑𝚎𝚗[(etvres)]𝚎𝚕𝚜𝚎𝚗𝚒𝚕)@premises¯\underline{@\;(\mathtt{if}\;\mathtt{not}(et\;\mathtt{in}\;es)\;\mathtt{then}\;[(et\;\longrightarrow\;v_{res})]\;\mathtt{else}\;\mathtt{nil})\;@\;premises\qquad\qquad\qquad~~~}
10 (op(𝚖𝚊𝚙𝙺𝚎𝚢𝚜(emap)))𝚒𝚏𝚗𝚘𝚝(et𝚒𝚗es)𝚝𝚑𝚎𝚗vres𝚎𝚕𝚜𝚎et{(op\;(\mathtt{mapKeys}(emap)))\;\longrightarrow\;\mathtt{if}\;\mathtt{not}(et\;\mathtt{in}\;es)\;\mathtt{then}\;v_{res}\;\mathtt{else}\;et}

Line 1 updates the rules of the language with the list computed in lines 2-9. Line 2 generates reduction rules such as λx.eλx.e\lambda x.e\longrightarrow\lambda x.e, for each value, as it is standard in big-step semantics. These rules are appended to those generated in lines 3-9. Line 3 selects all the reduction rules. Line 4 leaves out those rules that are not about a top-level expression operator. This skips contextual rules that take a step E[e]E[e]E[e]\longrightarrow E[e^{\prime}], which do not appear in big-step semantics. To do so, line 4 make use of Expression[(op_)]:self){\emph{Expression}}[(op\;\_)]:\;\emph{self}). As opop is bound to the operator we are focusing on (from line 2), this selector returns a list with one element if opop appears in Expression, and an empty list otherwise. This is the check we perform at line 4. Line 5 generates a new variable that will store the final value of the step. Line 6 assigns a new variable to each of the arguments in (es)(es). We do so creating a map emap. These new variables are the formal arguments of the new rule being generated (Line 9). Line 7-8 makes each of these variables evaluate to its corresponding argument in eses (line 8). For example, for the beta-reduction an argument of eses would be λx.e\lambda x.e and we therefore generate the premise e1λx.ee_{1}\longrightarrow\lambda x.e, where e1e_{1} is the new variable that we assigned to this argument with line 6. Line 7 skips generating the reduction premise if it is a variable that does not appear in ete_{t}. For example, in the translation of (if-true) (𝑖𝑓truee2e3)e2(\mathit{if}\;true\;e_{2}\;e_{3})\longrightarrow e_{2} we do not evaluate e3e_{3} at all. Line 9 handles the result of the overall small-step reduction. This result is evaluated to a value (vresv_{res}), unless it already appears in the arguments eses. The conclusion of the rule syncs with this, and we place vresv_{res} or ete_{t} in the target of the step accordingly. Line 9 also appends the premises from the original rule, as they contain conditions to be checked.

When we apply this algorithm to the simply typed λ\lambda-calculus with if-then-else we obtain: (we use standard notation rather than Tr\mathcal{L}\textendash\textsf{Tr} syntax)

last(l)last(l) returns the last element of a list, and @@ is list append.

The function is mostly a straightforward recursive traverse of terms, formulae, list of terms and list of formulae. The only elements to notice are that when 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦\mathit{uniquefy} detects a context that potentially contain the string strstr then it switches to 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦\mathit{uniquefy}^{\bullet}, which is a meta-operation that seeks for strstr. In turn, when 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦\mathit{uniquefy}^{\bullet} finds an argument in a position prescribed by strstr, then it switches to 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦\mathit{uniquefy}^{\dagger}, which is a meta-operation that is responsible for actually replace variables and record the association. 𝑧𝑖𝑝\mathit{zip} is a meta-operation that combines two lists. Of course, it may fail if the two lists do not have the same length. This happens in the scenario described above about \to and its number of argumets. 𝐶ℎ𝑒𝑐𝑘𝑍𝑖𝑝\mathit{CheckZip} performs just that check and can make the function fail.

Appendix 0.D Proof of Type Soundness

0.D.1 Progress Theorem

Theorem 0.D.1 (Canonical Form Lemmas)
  • e:𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\emptyset\vdash\;e:\mathtt{Language}, and ee is a value then e=𝚜𝚔𝚒𝚙e=\mathtt{skip}.

  • e:𝚁𝚞𝚕𝚎\emptyset\vdash\;e:\mathtt{Rule}, and ee is a value then e=re=r.

  • e:𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash\;e:\mathtt{Formula}, and ee is a value then e=fe=f.

  • e:𝚃𝚎𝚛𝚖\emptyset\vdash\;e:\mathtt{Term}, and ee is a value then e=te=t.

  • e:𝙻𝚒𝚜𝚝T\emptyset\vdash\;e:{\mathtt{List}}\;T, and ee is a value then e=𝚗𝚒𝚕e=𝚌𝚘𝚗𝚜v1v2e={\mathtt{nil}}\;\lor e={\mathtt{cons}}{\;v_{1}}{v_{2}}.

  • e:𝙼𝚊𝚙T1T2\emptyset\vdash\;e:\mathtt{Map}\;T_{1}\;T_{2}, and ee is a value then e=𝚖𝚊𝚙(v1,v2)e=\mathtt{map}(v_{1},v_{2}).

  • e:𝙾𝚙𝚝𝚒𝚘𝚗T1T2\emptyset\vdash\;e:\mathtt{Option}\;T_{1}{T_{2}}, and ee is a value then e=𝚗𝚘𝚝𝚑𝚒𝚗𝚐=𝚓𝚞𝚜𝚝ve=\mathtt{nothing}\;\lor=\mathtt{just}\;v.

  • e:𝚂𝚝𝚛𝚒𝚗𝚐\emptyset\vdash\;e:\mathtt{String}, and ee is a value then e=stre=str.

  • e:𝙾𝚙𝙽𝚊𝚖𝚎\emptyset\vdash\;e:\mathtt{OpName}, and ee is a value then e=opnamee=opname.

  • e:𝙿𝚛𝚎𝚍𝙽𝚊𝚖𝚎\emptyset\vdash\;e:\mathtt{PredName}, and ee is a value then e=prednamee=predname.

Proof

Each case is proved by case analysis on e:T\emptyset\vdash\;e:T. Each case is straightforward.

Theorem 0.D.2 (Progress Theorem Expressions)

For all , if e:T\emptyset\vdash e:T then either

  • e=ve=v, or

  • e=𝚎𝚛𝚛𝚘𝚛e=\mathtt{error}, or

  • for all V,V,\mathcal{L}, V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}, for some V,,eV^{\prime},\mathcal{L}^{\prime},e^{\prime}.

Proof

We prove the theorem by induction on the derivation of e:T\emptyset\vdash e:T. Let us assume the proviso of the theorem, that is (H1) e:T\emptyset\vdash e:T.

V;;(opname𝚎𝚛𝚛𝚘𝚛)V;;𝚎𝚛𝚛𝚘𝚛V;\mathcal{L}\;;\;(opname\;\mathtt{error})\longrightarrow V;\mathcal{L}\;;\;\mathtt{error}, for all V,V,\mathcal{L} because of the evaluation context (opnameE)(opname\;E). for all V,V,\mathcal{L}, V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}, for some V,,eV^{\prime},\mathcal{L}^{\prime},e^{\prime}. Then (opnamee)(opname\;e) takes a step by ctx-succ or ctx-lang-err.

Case 2 (t-rule-comp)
Since (H1) then we have e=(e1;re2)e=(e_{1};_{\textsf{r}}e_{2}) with e1:𝙻𝚒𝚜𝚝𝚁𝚞𝚕𝚎\emptyset\vdash e_{1}:{\mathtt{List}}\;\mathtt{Rule}. By IH, we have that V;;𝚎𝚛𝚛𝚘𝚛;re2V;;𝚎𝚛𝚛𝚘𝚛V;\mathcal{L}\;;\;\mathtt{error};_{\textsf{r}}e_{2}\longrightarrow V;\mathcal{L}\;;\;\mathtt{error}, for all V,V,\mathcal{L} because of the evaluation context E;reE;_{\textsf{r}}e. for all V,V,\mathcal{L}, V;;e1V;;e1V;\mathcal{L};e_{1}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{1}^{\prime}, for some V,,eV^{\prime},\mathcal{L}^{\prime},e^{\prime}. Then e1;re2e_{1};_{\textsf{r}}e_{2} takes a step by ctx-succ or ctx-lang-err.
Case 3 (t-seq)
Since (H1) then we have e=e1;e2e=e_{1};e_{2} with e1:𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\emptyset\vdash e_{1}:\mathtt{Language}. By IH, we have that e1=ve_{1}=v. By Canonical form e1=𝚜𝚔𝚒𝚙e_{1}=\mathtt{skip}. Then we have 𝚜𝚔𝚒𝚙;e2\mathtt{skip};e_{2} which by takes a step. e1=𝚎𝚛𝚛𝚘𝚛e_{1}=\mathtt{error}. Then we have 𝚎𝚛𝚛𝚘𝚛;e2\mathtt{error};e_{2} and by ctx-err we take a step to an error. for all V,V,\mathcal{L}, V;;e1V;;e1V;\mathcal{L};e_{1}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{1}^{\prime}, for some V,,eV^{\prime},\mathcal{L}^{\prime},e^{\prime}. Then by ctx-succ or ctx-lang-err, we take a step.
Case 4 (t-selector)
Since (H1) then we have that e1:𝙻𝚒𝚜𝚝T\emptyset\vdash e_{1}:{\mathtt{List}}\;T. By IH, we have that e1=ve_{1}=v. By Canonical form e1e_{1} can have two forms: e1=𝚗𝚒𝚕e_{1}={\mathtt{nil}} Then we apply r-selector-nil takes a step. e1=𝚌𝚘𝚗𝚜v1v2e_{1}={\mathtt{cons}}{\;v_{1}}{v_{2}}. Then we have two cases: ether 𝑚𝑎𝑡𝑐ℎ(v1,p,\mathit{match}(v_{1},p,) succeeds, then we apply r-selector-cons-ok and take a step, or 𝑚𝑎𝑡𝑐ℎ(v1,p,\mathit{match}(v_{1},p,) fails, then we apply r-selector-cons-fail and take a step. e1=𝚎𝚛𝚛𝚘𝚛e_{1}=\mathtt{error}. Then by ctx-err we take a step to an error. for all V,V,\mathcal{L}, V;;e1V;;e1V;\mathcal{L};e_{1}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{1}^{\prime}, for some V,,eV^{\prime},\mathcal{L}^{\prime},e^{\prime}. Then by ctx-succ or ctx-lang-err, we take a step. The case for selectors with keep are analogous.
Case 5 (t-uniquefy)
Since (H1) then we have that e1:𝚁𝚞𝚕𝚎\emptyset\vdash e_{1}:\mathtt{Rule}, e2:𝙼𝚊𝚙𝙾𝚙𝙽𝚊𝚖𝚎(𝙻𝚒𝚜𝚝𝚂𝚝𝚛𝚒𝚗𝚐)\emptyset\vdash e_{2}:\mathtt{Map}\;\mathtt{OpName}\;({\mathtt{List}}\;\mathtt{String}). By IH on e1e_{1}, we have that e1=v1e_{1}=v_{1}. By Canonical form e1=re_{1}=r. By IH on e2e_{2} we have the three cases: e2=v2e_{2}=v_{2}. Then we there are two cases: Either 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦\mathit{uniquefy} succeeds and we apply r-uniquefy-ok to take a step, or fails and we apply r-uniquefy-fail to take a step. e2=𝚎𝚛𝚛𝚘𝚛e_{2}=\mathtt{error}. Then by ctx-err we take a step to an error. for all V,V,\mathcal{L}, V;;e2V;;e2V;\mathcal{L};e_{2}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{2}^{\prime}, for some V,,e2V^{\prime},\mathcal{L}^{\prime},e_{2}^{\prime}. Then by ctx-succ or ctx-lang-err, we take a step. e1=𝚎𝚛𝚛𝚘𝚛e_{1}=\mathtt{error}. Then by ctx-err we take a step to an error. for all V,V,\mathcal{L}, V;;e1V;;e1V;\mathcal{L};e_{1}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{1}^{\prime}, for some V,,e1V^{\prime},\mathcal{L}^{\prime},e_{1}^{\prime}. Then by ctx-succ or ctx-lang-err, we take a step. The case of e2:𝙼𝚊𝚙𝙿𝚛𝚎𝚍𝙽𝚊𝚖𝚎(𝙻𝚒𝚜𝚝𝚂𝚝𝚛𝚒𝚗𝚐)\emptyset\vdash e_{2}:\mathtt{Map}\;\mathtt{PredName}\;({\mathtt{List}}\;\mathtt{String}) is analogous.
Case 6 (t-tick)
Since (H1) then we have that e1:T\emptyset\vdash e_{1}:T, e2:𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖\emptyset\vdash e_{2}:{\mathtt{List}}\;\mathtt{Term}. By IH on e1e_{1}, we have that e1=v1e_{1}=v_{1}. By IH on e2e_{2}: e2=v2e_{2}=v_{2}. Then we have two cases depending on TT: * T=𝚃𝚎𝚛𝚖T=\mathtt{Term}. By Canonical forms, we have that e1e_{1} can be of the following forms: · (opnamev1)(opname\;v_{1}^{\prime}). Then we apply LABEL:r-tick-opname and take a step. · X{X}. Then we apply LABEL:r-tick-var and take a step. · (zv1)({z}v_{1}^{\prime}). Then we apply LABEL:r-tick-abs and take a step. · v1[v1′′/z]v_{1}^{\prime}[v_{1}^{\prime\prime}/{z}]. Then we apply LABEL:r-tick-sub and take a step. * T=𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖T={\mathtt{List}}\;\mathtt{Term}. By Canonical form e1e_{1} can have two forms: · e1=𝚗𝚒𝚕e_{1}={\mathtt{nil}}. Then we apply LABEL:r-tick-nil takes a step. · e1=𝚌𝚘𝚗𝚜v1v2e_{1}={\mathtt{cons}}{v_{1}}{v_{2}}. Then we apply LABEL:r-tick-cons takes a step. e2=𝚎𝚛𝚛𝚘𝚛e_{2}=\mathtt{error}. Then by ctx-err we take a step to an error. for all V,V,\mathcal{L}, V;;e2V;;e2V;\mathcal{L};e_{2}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{2}^{\prime}, for some V,,e2V^{\prime},\mathcal{L}^{\prime},e_{2}^{\prime}. Then by ctx-succ or ctx-lang-err, we take a step. e1=𝚎𝚛𝚛𝚘𝚛e_{1}=\mathtt{error}. Then by LABEL:ctx-err we take a step to an error. for all V,V,\mathcal{L}, V;;e1V;;e1V;\mathcal{L};e_{1}\longrightarrow V^{\prime};\mathcal{L}^{\prime};e_{1}^{\prime}, for some V,,e1V^{\prime},\mathcal{L}^{\prime},e_{1}^{\prime}. Then by ctx-succ or ctx-lang-err, we take a step.
All other cases follow similar lines as above. ∎

Theorem 0.D.3 (Progress Theorem for Configurations)

For all , if V;;e\emptyset\vdash V;\mathcal{L};e then either

  • e=𝚜𝚔𝚒𝚙e=\mathtt{skip}, or

  • e=𝚎𝚛𝚛𝚘𝚛e=\mathtt{error}, or

  • V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}, for some ee^{\prime}.

Proof

Let us assume the proviso: V;;e\emptyset\vdash V;\mathcal{L};e. Then we have e:𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\emptyset\vdash e:\mathtt{Language}. By Progress Theorem for Expressions, we have that

  • e=ve=v. By Canonical forms, e=𝚜𝚔𝚒𝚙e=\mathtt{skip}.

  • e=𝚎𝚛𝚛𝚘𝚛e=\mathtt{error}, which satisfies the theorem.

  • V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}, for some ee^{\prime}, which satisfies the theorem.

0.D.2 Subject Reduction Theorem

Lemma 1 (Substitution Lemma)

if Γ,x:Te:T\Gamma,x:T\vdash e:T^{\prime} and v:T\emptyset\vdash v:T then Γe[v/x]:T\Gamma\vdash e[v/x]:T^{\prime}.

Proof

The proof is by induction on the derivation of Γ,x:Te:T\Gamma,x:T\vdash e:T^{\prime}. As usual, the case for variables (t-var) relies on a standard weakening lemma: Γe:T\Gamma\vdash e:T^{\prime} and xx is not in the free variables of ee then Γ,x:Te:T\Gamma,x:T\vdash e:T^{\prime}, which can be proved by induction on the derivation of Γe:T\Gamma\vdash e:T^{\prime}. An aspect that differs from a standard proof is that our substitution does not replace all instances of variables 𝑠𝑒𝑙𝑓\mathit{self}, 𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠\mathit{premises}, and 𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛\mathit{conclusion} in certain context. Then extra care must be taken in the substitution lemma because the substituted expression may still have those as free variables. The type system covers for those cases because it augments the type environment with Γrule\Gamma_{\textsf{rule}}.

Lemma 2 (Pattern-matching typing and reduction)

if p:TΓ\emptyset\vdash p:T\Rightarrow\Gamma^{\prime} and 𝑚𝑎𝑡𝑐ℎ(v,p)=θ\mathit{match}(v,p)=\theta then for all x:TΓx:T^{\prime}\in\Gamma^{\prime}, [x/v]θ[x/v^{\prime}]\in\theta and v:T\emptyset\vdash v^{\prime}:T^{\prime}.

Proof

The proof is by induction on the derivation of p:TΓ\emptyset\vdash p:T\Rightarrow\Gamma^{\prime}. Each case is straightforward. ∎

Lemma 3 (𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦lf\mathit{uniquefy}_{\textsf{lf}} produces well-typed results or fails)

𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦lf(lf,m,str,mr)=res\mathit{uniquefy}_{\textsf{lf}}(lf,m,str,mr)=res

lf:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash lf^{\prime}:{\mathtt{List}}\;\mathtt{Formula}, and mr:𝙼𝚊𝚙𝙼𝚎𝚝𝚊𝚅𝚊𝚛(𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖)\emptyset\vdash mr^{\prime}:\mathtt{Map}\;\mathtt{MetaVar}\;({\mathtt{List}}\;\mathtt{Term}). res=𝑓𝑎𝑖𝑙res=\mathit{fail}.

Proof
Straightforward induction on the definition of 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦lf\mathit{uniquefy}_{\textsf{lf}}. Most cases rely on the analogous lemmas for formulae, terms, list of terms and list of formulae: f:𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash f^{\prime}:\mathtt{Formula}, and mr:𝙼𝚊𝚙𝚃𝚎𝚛𝚖(𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖)\emptyset\vdash mr^{\prime}:\mathtt{Map}\;\mathtt{Term}\;({\mathtt{List}}\;\mathtt{Term}). res=𝑓𝑎𝑖𝑙res=\mathit{fail}. 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦t(t,m,str,mr)=res\mathit{uniquefy}_{\textsf{t}}(t,m,str,mr)=res res=(t,mr)res=(t^{\prime},mr^{\prime}) such that t:𝚃𝚎𝚛𝚖\emptyset\vdash t^{\prime}:\mathtt{Term}, and mr:𝙼𝚊𝚙𝚃𝚎𝚛𝚖(𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖)\emptyset\vdash mr^{\prime}:\mathtt{Map}\;\mathtt{Term}\;({\mathtt{List}}\;\mathtt{Term}). res=𝑓𝑎𝑖𝑙res=\mathit{fail}. 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦lt(lt,m,str,mr)=res\mathit{uniquefy}_{\textsf{lt}}(lt,m,str,mr)=res res=(lt,mr)res=(lt^{\prime},mr^{\prime}) such that lt:𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖\emptyset\vdash lt^{\prime}:{\mathtt{List}}\;\mathtt{Term}, and mr:𝙼𝚊𝚙𝚃𝚎𝚛𝚖(𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖)\emptyset\vdash mr^{\prime}:\mathtt{Map}\;\mathtt{Term}\;({\mathtt{List}}\;\mathtt{Term}). res=𝑓𝑎𝑖𝑙res=\mathit{fail}. Each can be proved with a straightforward induction on the definition of 𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦𝒳\mathit{uniquefy}_{\mathcal{X}} where 𝒳{f,t,lt}\mathcal{X}\in\{\textsf{f,t,lt}\}. ∎
Lemma 4 (Compositionality of \vdash)
if E[e]:T\emptyset\vdash E[e]:T then there exits TT^{\prime} such that e:T\emptyset\vdash e:T^{\prime} and for all ee^{\prime} if e:T\emptyset\vdash e^{\prime}:T^{\prime} then E[e]:T\emptyset\vdash E[e^{\prime}]:T.
Proof
Proof is by induction on the structure of EE. Each case is straightforward.
Theorem 0.D.4 (Subject Reduction (@\longrightarrow_{\mathtt{@}}))
V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, e:T\emptyset\vdash e:T, and V;;e@V;;eV;\mathcal{L}\;;\;e\longrightarrow_{\mathtt{@}}V^{\prime};\mathcal{L}^{\prime}\;;\;e^{\prime} then V𝑣𝑎𝑟𝑠()=V^{\prime}\cap\mathit{vars}(\mathcal{L}^{\prime})=\emptyset, and e:T\emptyset\vdash e^{\prime}:T.
Proof
Let us assume the proviso of the theorem, that is, (H1) V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, (H2) Γe:T\Gamma\vdash e:T, and (H3) V;;eV;;eV;\mathcal{L}\;;\;e\longrightarrow V^{\prime};\mathcal{L}^{\prime}\;;\;e^{\prime}. Case analysis on (H3).
Case 7 (r-seq-ok)
V;;(𝚜𝚔𝚒𝚙;e)V;;eV;\mathcal{L}\;;\;(\mathtt{skip};e)\longrightarrow V;\mathcal{L}\;;\;e. We need to prove 𝑣𝑎𝑟𝑠()V\mathit{vars}(\mathcal{L})\subseteq V, which we already have by (H1). We need to prove \vdash\mathcal{L}, which we have by (H2). We have to prove that Γe:T\Gamma\vdash e:T where Γ(𝚜𝚔𝚒𝚙;e):T\Gamma\vdash(\mathtt{skip};e):T. By t-seq we have that Γ(𝚜𝚔𝚒𝚙;e):𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\Gamma\vdash(\mathtt{skip};e):\mathtt{Language} (i.e. T=𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎T=\mathtt{Language}), Γe:𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\Gamma\vdash e:\mathtt{Language}.
Case 8 (r-newar)
V;(G,R);𝚗𝚎𝚠𝚅𝚊𝚛str@V{X};;XV;(G,R)\;;\;\mathtt{newVar}\;str\longrightarrow_{\mathtt{@}}V\cup\{{X^{\prime}}\};\mathcal{L}\;;\;{X^{\prime}}. We need to prove 𝑣𝑎𝑟𝑠()V{X}\mathit{vars}(\mathcal{L})\subseteq V\cup\{{X^{\prime}}\}, which we have because by (H1), we have that 𝑣𝑎𝑟𝑠()V\mathit{vars}(\mathcal{L})\subseteq V, and we additionally we have that X𝑣𝑎𝑟𝑠(){X^{\prime}}\not\in\mathit{vars}(\mathcal{L}). We have to prove that ΓX:𝚃𝚎𝚛𝚖\Gamma\vdash{X^{\prime}}:\mathtt{Term} because Γ𝚗𝚎𝚠𝚅𝚊𝚛:𝚃𝚎𝚛𝚖\Gamma\vdash\mathtt{newVar}:\mathtt{Term}. This holds thanks to t-metaVar.
Case 9 (r-rule-comp)
V;;v;re@V;;eθrule(v)V;\mathcal{L}\;;\;v;_{\textsf{r}}e\longrightarrow_{\mathtt{@}}V;\mathcal{L}\;;\;e\theta_{\textsf{rule}}^{(v)}. We need to prove V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, which we have because by (H1). We have to prove that (#) eθrule(v):𝚁𝚞𝚕𝚎\emptyset\vdash e\theta_{\textsf{rule}}^{(v)}:\mathtt{Rule} when (*) v;re:𝚁𝚞𝚕𝚎\emptyset\vdash v;_{\textsf{r}}e:\mathtt{Rule}. From (*) we infer (HRULE) v:𝚁𝚞𝚕𝚎\emptyset\vdash v:\mathtt{Rule} and Γrulee:𝚁𝚞𝚕𝚎\Gamma_{\textsf{rule}}\vdash\;e:\mathtt{Rule}, that is (HE) 𝑠𝑒𝑙𝑓:𝚁𝚞𝚕𝚎,𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊,𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛:𝙵𝚘𝚛𝚖𝚞𝚕𝚊e:𝚁𝚞𝚕𝚎\mathit{self}:\mathtt{Rule},\mathit{premises}:{\mathtt{List}}\;\mathtt{Formula},\mathit{conclusion}:\mathtt{Formula}\vdash\;e:\mathtt{Rule}. By Canonical Form Lemma, from (HRULE) we infer that v=\inferencev1v2v=\inference{v_{1}}{v_{2}} and since it is typeable (HRULE), by t-rule have v1:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash v_{1}:{\mathtt{List}}\;\mathtt{Formula} and v2:𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash v_{2}:\mathtt{Formula}. eθrule(v)=e[r/𝑠𝑒𝑙𝑓,v1/𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠,v2/𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛]=e[v/𝑠𝑒𝑙𝑓][v1/𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠][v2/𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛]e\theta_{\textsf{rule}}^{(v)}=e[r/\mathit{self},v_{1}/\mathit{premises},v_{2}/\mathit{conclusion}]=e[v/\mathit{self}][v_{1}/\mathit{premises}][v_{2}/\mathit{conclusion}]. Given (HE), and given (HRULE), by Substitution Lemma we have (HE1HE_{1}) 𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊,𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛:𝙵𝚘𝚛𝚖𝚞𝚕𝚊e[v/𝑠𝑒𝑙𝑓]:𝚁𝚞𝚕𝚎\mathit{premises}:{\mathtt{List}}\;\mathtt{Formula},\mathit{conclusion}:\mathtt{Formula}\vdash\;e[v/\mathit{self}]:\mathtt{Rule}. Given (HE1HE_{1}), and given Γv1:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊\Gamma\vdash v_{1}:{\mathtt{List}}\;\mathtt{Formula} , by Substitution Lemma we have (HE2HE_{2}) 𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛:𝙵𝚘𝚛𝚖𝚞𝚕𝚊e[v/𝑠𝑒𝑙𝑓][v1/𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠]:𝚁𝚞𝚕𝚎\mathit{conclusion}:\mathtt{Formula}\vdash\;e[v/\mathit{self}][v_{1}/\mathit{premises}]:\mathtt{Rule}. Given (HE2HE_{2}), and given v2:𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash v_{2}:\mathtt{Formula} , by Substitution Lemma we have e[v/𝑠𝑒𝑙𝑓][v1/𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠][v2/𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛]:𝚁𝚞𝚕𝚎\emptyset\vdash\;e[v/\mathit{self}][v_{1}/\mathit{premises}][v_{2}/\mathit{conclusion}]:\mathtt{Rule}.
Case 10 (r-selector-nil)
V;;𝚗𝚒𝚕[p]:e@V;;𝚗𝚒𝚕V;\mathcal{L}\;;\;{\mathtt{nil}}[p]:\;e\longrightarrow_{\mathtt{@}}V;\mathcal{L}\;;\;\mathtt{nil}. We need to prove V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, which we have because by (H1). We have to prove that (#) Γ𝚗𝚒𝚕:𝙻𝚒𝚜𝚝T\Gamma\vdash\mathtt{nil}:{\mathtt{List}}\;T when (*) Γ𝚗𝚒𝚕[p]:e:𝙻𝚒𝚜𝚝T\Gamma\vdash{\mathtt{nil}}[p]:\;e:{\mathtt{List}}\;T. Thanks to t-emptyList this holds.
V;;(𝑐𝑜𝑛𝑠eθθ(v2[p]:e))V;\mathcal{L}\;;\;(\mathit{cons}^{*}\;{e\theta\theta^{\prime}}\;{({v_{2}}[p]:\;e)}). We need to prove V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, which we have because by (H1). We have to prove that (#) (𝑐𝑜𝑛𝑠eθθ(v2[p]:e)):𝙻𝚒𝚜𝚝T\emptyset\vdash(\mathit{cons}^{*}\;{e\theta\theta^{\prime}}\;{({v_{2}}[p]:\;e)}):{\mathtt{List}}\;T^{\prime} when (*) Γ(𝚌𝚘𝚗𝚜v1v2)[p]:e:𝙻𝚒𝚜𝚝T\Gamma\vdash{(\mathtt{cons}\;v_{1}\;v_{2})}[p]:\;e:{\mathtt{List}}\;T^{\prime}. By t-selector we have that Γ(𝚌𝚘𝚗𝚜v1v2):𝙻𝚒𝚜𝚝T\Gamma\vdash(\mathtt{cons}\;v_{1}\;v_{2}):{\mathtt{List}}\;T, and therefore by t-cons, we have that Γv1:T\Gamma\vdash v_{1}:T. We do a case analysis on whether T=𝚁𝚞𝚕𝚎T=\mathtt{Rule} or not, to prove in both cases that Γeθθ:𝙾𝚙𝚝𝚒𝚘𝚗T\Gamma\vdash e\theta\theta^{\prime}:\mathtt{Option}\;T^{\prime}. T=𝚁𝚞𝚕𝚎T=\mathtt{Rule}: By Canonical Form, then we have that v1=\inferencev1v2v_{1}=\inference{v_{1}^{\prime}}{v_{2}^{\prime}}. Then θ=[v1/𝑠𝑒𝑙𝑓,v1/𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠,v2/𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛]\theta^{\prime}=[v_{1}/\mathit{self},v_{1}^{\prime}/\mathit{premises},v_{2}^{\prime}/\mathit{conclusion}]. From (*) we infer that Γ,𝑠𝑒𝑙𝑓:𝚁𝚞𝚕𝚎,𝑝𝑟𝑒𝑚𝑖𝑠𝑒𝑠:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊,𝑐𝑜𝑛𝑐𝑙𝑢𝑠𝑖𝑜𝑛:𝙵𝚘𝚛𝚖𝚞𝚕𝚊e2:𝙾𝚙𝚝𝚒𝚘𝚗T\Gamma^{\prime},\mathit{self}:\mathtt{Rule},\mathit{premises}:{\mathtt{List}}\;\mathtt{Formula},\mathit{conclusion}:\mathtt{Formula}\vdash\;e_{2}:\mathtt{Option}\;T^{\prime}, where Γ\Gamma^{\prime} comes from the pattern-matching. By applying the same reasoning as in r-rule-comp, we can apply the Substitution lemma three times to have Γeθ:𝙾𝚙𝚝𝚒𝚘𝚗T\Gamma^{\prime}\vdash e\theta^{\prime}:\mathtt{Option}\;T^{\prime}. By Lemma 2 (pattern-matching correctness) we have that for all (x:T′′)Γ(x:T^{\prime\prime})\in\Gamma^{\prime} there is [x/v′′]θ[x/v^{\prime\prime}]\in\theta such that v′′:T′′\emptyset\vdash v^{\prime\prime}:T^{\prime\prime}. Then, for all such (x:T′′)Γ(x:T^{\prime\prime})\in\Gamma^{\prime} we can use the Substitution Lemma to substitute its [x/v′′][x/v^{\prime\prime}], and end up with eθθ:𝙾𝚙𝚝𝚒𝚘𝚗T\emptyset\vdash e\theta\theta^{\prime}:\mathtt{Option}\;T^{\prime}. T𝚁𝚞𝚕𝚎T\not=\mathtt{Rule}: Then θ=[v1/𝑠𝑒𝑙𝑓]\theta^{\prime}=[v_{1}/\mathit{self}] and by Substitution lemma we have Γeθ:𝙾𝚙𝚝𝚒𝚘𝚗T\Gamma^{\prime}\vdash e\theta^{\prime}:\mathtt{Option}\;T^{\prime}. By pattern-matching correctness, the same reasoning as in the previous case leads us to eθθ:𝙾𝚙𝚝𝚒𝚘𝚗T\emptyset\vdash e\theta\theta^{\prime}:\mathtt{Option}\;T^{\prime}. As now we know that (*) Γeθθ:𝙾𝚙𝚝𝚒𝚘𝚗T\Gamma\vdash e\theta\theta^{\prime}:\mathtt{Option}\;T^{\prime} in all cases. If we expand (𝑐𝑜𝑛𝑠eθθ(v2[p]:e))(\mathit{cons}^{*}\;{e\theta\theta^{\prime}}\;{({v_{2}}[p]:\;e)}) we have 𝚒𝚏(𝚒𝚜𝙽𝚘𝚝𝚑𝚒𝚗𝚐eθθ)𝚝𝚑𝚎𝚗(v2[p]:e)𝚎𝚕𝚜𝚎𝚌𝚘𝚗𝚜(𝚐𝚎𝚝eθθ)(v2[p]:e)\mathtt{if}\;(\mathtt{isNothing}\;e\theta\theta^{\prime})\;\mathtt{then}\;({v_{2}}[p]:\;e)\;\mathtt{else}\;\mathtt{cons}\;(\mathtt{get}\;e\theta\theta^{\prime})\;({v_{2}}[p]:\;e). Here 𝚒𝚜𝙽𝚘𝚝𝚑𝚒𝚗𝚐\mathtt{isNothing} and 𝚐𝚎𝚝\mathtt{get} are applied to eθθe\theta\theta^{\prime} of type 𝙾𝚙𝚝𝚒𝚘𝚗T\mathtt{Option}\;T^{\prime}, therefore are well-typed. Also, both branches of the if return an expression of type 𝙻𝚒𝚜𝚝T{\mathtt{List}}\;T^{\prime}.
Case 12 (r-uniquefy-ok)
V;;𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(𝑙𝑓,v1,str)(x,y):e@V;;e[𝑙𝑓/x,v2/y]V;\mathcal{L}\;;\;\mathtt{uniquefy}(\mathit{lf},v_{1},str)\Rightarrow(x,y):e\longrightarrow_{\mathtt{@}}V;\mathcal{L}\;;\;e[\mathit{lf}^{\prime}/x,v_{2}/y]. We need to prove V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, which we have because by (H1). We have to prove that (#) Γe[𝑙𝑓/x,v2/y]:T\Gamma\vdash e[\mathit{lf}^{\prime}/x,v_{2}/y]:T when (*) Γ𝚞𝚗𝚒𝚚𝚞𝚎𝚏𝚢(r,v,str)(x,y):e:T\Gamma\vdash\mathtt{uniquefy}(r,v,str)\Rightarrow(x,y):e:T. By r-uniquefy-ok we have (r,m)=𝑢𝑛𝑖𝑞𝑢𝑒𝑓𝑦r(r,v,str,𝚎𝚖𝚙𝚝𝚢𝙼𝚊𝚙)(r^{\prime},m)=\mathit{uniquefy}_{\textsf{r}}(r,v,str,\mathtt{emptyMap}), and by Lemma 3 we have that
𝑙𝑓:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊\emptyset\vdash\mathit{lf^{\prime}}:{\mathtt{List}}\;\mathtt{Formula}, and v2:𝙼𝚊𝚙𝚃𝚎𝚛𝚖(𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖)\emptyset\vdash v_{2}:\mathtt{Map}\;\mathtt{Term}\;({\mathtt{List}}\;\mathtt{Term}). By t-uniquefy we have that Γ,x:𝙻𝚒𝚜𝚝𝙵𝚘𝚛𝚖𝚞𝚕𝚊,y:𝙼𝚊𝚙𝚃𝚎𝚛𝚖(𝙻𝚒𝚜𝚝𝚃𝚎𝚛𝚖)e3:T\Gamma,x:{\mathtt{List}}\;\mathtt{Formula},y:\mathtt{Map}\;\mathtt{Term}\;({\mathtt{List}}\;\mathtt{Term})\vdash\;e_{3}:T. By Substitution Lemma, we then have Γe[𝑙𝑓/x,v2/y]:T\Gamma\vdash e[\mathit{lf}^{\prime}/x,v_{2}/y]:T.

All other cases are analogous.

Theorem 0.D.5 (Subject Reduction (\longrightarrow))

For all VV, VV^{\prime}, \mathcal{L}, \mathcal{L}^{\prime}, ee, ee^{\prime}, if V;;e\emptyset\vdash V;\mathcal{L};e and V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime} then V;;e\emptyset\vdash V^{\prime};\mathcal{L}^{\prime};e^{\prime}.

Proof

Let us assume the proviso of the theorem and have (H1) V;;e\emptyset\vdash V;\mathcal{L};e and V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}. The proof is by case analysis on the derivation of V;;eV;;eV;\mathcal{L};e\longrightarrow V^{\prime};\mathcal{L}^{\prime};e^{\prime}

Case 13 (ctx-succ)

V;;E[e]V;;E[e]V;\mathcal{L};E[e]\longrightarrow V^{\prime};\mathcal{L}^{\prime};E[e^{\prime}] when (H2) V;;e@V;;eV;\mathcal{L};e\longrightarrow_{\mathtt{@}}V^{\prime};\mathcal{L}^{\prime};e^{\prime}. From (H1) we know that (H6) E[e]:T\emptyset\vdash E[e]:T, for some TT. Then we can apply Lemma 4 to have that (H3) e:T\emptyset\vdash e:T^{\prime}, for some TT^{\prime}. With (H2) and (H3) we can apply Subject Reduction for @\longrightarrow_{\mathtt{@}} and obtain that (H4) e:T\emptyset\vdash e:T^{\prime} and (H5) V𝑣𝑎𝑟𝑠()=V^{\prime}\cap\mathit{vars}(\mathcal{L}^{\prime})=\emptyset. By Lemma 4, since we have (H6) and (H4) we can derive E[e]:T\emptyset\vdash E[e^{\prime}]:T, and since we have (H5), we can derive V;;E[e]\emptyset\vdash V;\mathcal{L};E[e^{\prime}].

Case 14 (ctx-lang-err)

V;;E[e]V;;𝚎𝚛𝚛𝚘𝚛V;\mathcal{L}\;;\;E[e]\longrightarrow V;\mathcal{L}\;;\;\mathtt{error}. (H1) implies V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset and \vdash\mathcal{L}. We need to prove V𝑣𝑎𝑟𝑠()=V\cap\mathit{vars}(\mathcal{L})=\emptyset, which we already have, and \vdash\mathcal{L}, which we already have. We need to prove Γ𝚎𝚛𝚛𝚘𝚛:𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎\Gamma\vdash\mathtt{error}:\mathtt{Language}, which we can prove with (t-error).

Case 15 (ctx-err)

Similar lines as ctx-lang-err.

0.D.3 Type Soundness

Theorem 0.D.6 (Type Soundness)

For all Γ\Gamma, VV, \mathcal{L}, ee, if V;;e\vdash V;\mathcal{L};e then V;;eV;;eV;\mathcal{L};e\longrightarrow^{*}V^{\prime};\mathcal{L}^{\prime};e^{\prime} s.t. i) e=𝚜𝚔𝚒𝚙e^{\prime}=\mathtt{skip}, ii) e=𝚎𝚛𝚛𝚘𝚛e^{\prime}=\mathtt{error}, or iii) V;;eV′′;′′;e′′V^{\prime};\mathcal{L}^{\prime};e^{\prime}\longrightarrow V^{\prime\prime};\mathcal{L}^{\prime\prime};e^{\prime\prime}, for some e′′e^{\prime\prime}.

The proof is straightforward once we have the Subject Reduction (\longrightarrow) theorem, and the Progress for Configuration theorem, and that typeability is preserved in multiple steps (provable by straightforward induction on the derivation of \longrightarrow^{*}).

Appendix 0.E Let-Binding and Match in Tr\mathcal{L}\textendash\textsf{Tr}

𝚕𝚎𝚝x=e1𝚒𝚗e2𝚑𝚎𝚊𝚍([e1][x]:e2)\mathtt{let}\;x=e_{1}\;\mathtt{in}\;e_{2}\equiv\mathtt{head}\;({[e_{1}]}[x]:\;e_{2})

The pattern-matching that we use is unary-branched and either succeeds or throws an error.

𝚖𝚊𝚝𝚌𝚑e1𝚠𝚒𝚝𝚑pe2\mathtt{match}\;e_{1}\;\mathtt{with}\;p\Rightarrow e_{2} letx=([e1][p]:e2))inif(isEmptyx)thenerrorelseheadx