Table of Links
-
Translating To Sequent Calculus
-
Evaluation Within a Context
-
Insights
5.1 Evaluation Contexts are First Class
5.3 Let-Bindings are Dual to Control Operators
5.4 The Case-of-Case Transformation
5.5 Direct and Indirect Consumers
-
Conclusion, Data Availability Statement, and Acknowledgments
A. The Relationship to the Sequent Calculus
C. Operational Semantics of label/goto
2 Translating To Sequent Calculus
In this section, we introduce Fun, an expression-oriented functional programming language, together with its translation into the sequent-calculus-based intermediate language Core. We present both languages and the translation function [β] in multiple steps, starting with arithmetic expressions and adding more features in later subsections. We postpone the typing rules for both languages until Section 4.
2.1 Arithmetic Expressions
We begin with arithmetic expressions which consist of variables, integer literals, binary operators and ifz, a conditional expression which checks whether its first argument is equal to zero. The syntax of arithmetic expressions for Fun and Core is given in Definition 2.1.
In Fun there is only one syntactic category: termsπ‘. These terms can either be variables π₯, literals βπβ, binary operators π‘ + π‘, π‘ β π‘ and π‘ β π‘, or a conditional ifz(π‘, π‘0, π‘1). This conditional evaluates to π‘0 if π‘ evaluates to β0β, or to π‘1 otherwise. In contrast to this single category, Core uses three different syntactic categories: producers π, consumers π and statements π . These categories are directly inherited from the πππΛ-calculus, and it is important to understand their differences:
Producers All constructs in Core which construct or produce an element of some type belong to the syntactic category of producers. In other words, producers correspond to βintroduction formsβ or βproof termsβ, and every term of the language Fun is translated to a producer in Core.
Consumers Consumers are probably less intuitive than producers since they do not correspond directly to any term of the language Fun. The basic idea is that if some consumerπ has type π, then π consumes or destructs a producer of type π. If you have encountered evaluation contexts or continuations before, then it is helpful to think of consumers of type π as continuations or evaluation contexts for a producer of type π. And if you are familiar with the Curry-Howard correspondence, then you can think of consumers as refutations or direct evidence that a proposition is false.
Statements Statements are the ingredient which make computation happen; without statements, we would only have static objects without any dynamic behavior. Here is a non-exhaustive list of examples for statements: Every IO action which reads from or prints to the console or a file should be represented as a statement in Core. Computations on primitive types such as machine integers should be statements. Finally, everything which is a redex in an expression-based language should also correspond to a statement in Core. Since statements themselves only compute and do not return anything they do not have a type.
After these general remarks, let us now look at how arithmetic expressions are represented in the language Core. Variables π₯ and literals βπβ both belong to the category of producers, but binary operators are represented as statements β(π1, π2;π). First, let us explain why they are represented as statements instead of producers. The idea is that a binary operator on primitive integers has to be evaluated directly by the arithmetic logic unit (ALU) of the underlying machine. And any operation which directly invokes the machine should belong to the same syntactic category as a print or other IO instruction: statements. The machine does not return a result; rather, it reads inputs from registers and makes the result available in a register for further computation. This is also reflected in the second surprising aspect: the operator has three instead of two arguments. The two producers π1 and π2 correspond to the usual arguments, but the third consumer argument π says what should happen to the result once the binary operator has been evaluated. This is similar to the continuation argument of a function in continuation-passing style. Binary operators β(π1, π2;π) also display a syntactic convention we use: whenever some construct has arguments of different syntactic categories, we use a semicolon instead of a comma to separate them.
We can immediately see that the result of Jπ1 + π2K should contain the statement +([π1] [π2]; ?), but we still have to figure out which consumer to plug in at the third-argument place, and how to convert this statement into a producer. We can do this with a π-abstraction in Core, which turns a statement into a producer while binding a covariable πΌ: ππΌ. + ([π1], [π2]; πΌ).
The statement ifz works similarly to binary operators: It is a computation which checks if the producer π is zero and then continues with one of its two branches. These branches are also statements, indicating which computation to run after the condition has been evaluated. In the language Fun the two branches were terms, so we now have to find a way to transform two producers into two statements. We can do this by using a cut β¨π | πβ© which combines a producer and a consumer of the same type to obtain a statement in each branch: ifz([π‘1], β¨[π‘2] | ? β©, β¨[π‘3] | ? β©). We can then use the same covariable πΌ in both statements to represent the fact that the we want the result in either branch to return to the same point in the program; we use a surrounding π-binding again to bind this covariable: ππΌ.ifz([π‘1], β¨[π‘2] | πΌβ©, β¨[π‘3] | πΌβ©).
Let us now see how arithmetic expressions are evaluated. Definition 2.2 introduces the syntax of values and covalues, and shows how to reduce immediate redexes. We use a simple syntactic convention here: The metavariable for a value of termsπ‘ is π±, the values of producers π are written π and the covalues which correspond to consumersπ are written π . We use the symbol β² for reduction in both Fun and Core (and write β² β when multiple steps are performed at once).
Values and the evaluation of redexes in Fun is straightforward, the only noteworthy aspect is that the two rules for ifz(Β·, π‘1, π‘2) do not require π‘1 and π‘2 to be values. Thus, let us proceed with the discussion of the language Core.
The first interesting aspect of the language Core is that there are both values and covalues. This can be explained by the role that values play in operational semantics: they specify the subset of terms that we are allowed to substitute for a variable. And since we have both variables which stand for producers and covariables which stand for consumers, we need both values and covalues as the respective subsets which we are allowed to substitute for a variable or covariable.
The second interesting aspect of the language Core is that only statements are reduced, not producers or consumers. This substantiates our remark from above that it is statements that introduce dynamism into the language by driving computation. It also contributes to the feeling that reduction in the language is close to the evaluation of an abstract machine and that the statements of Core correspond to the states of such an abstract machine.
We are still faced with a small problem when we want to show that a term of Fun evaluates to the same result as its translation into Core: We have only specified the reduction for statements but not for producers. We can easily solve this problem by introducing a special covariable β which acts as the βtop-levelβ consumer of an evaluation. Using β we can then evaluate the statement β¨[π‘] | ββ© instead of the producer [π‘].
Example 2.1. Consider the two terms β2β β β3β and ifz(β2β, β5β, β10β) of Fun. Their respective translations into Core are ππΌ. β (β2β, β3β; πΌ) and ππΌ.ifz(β2β, β¨β5β | πΌβ©, β¨β10β | πΌβ©). When we wrap them into a statement using the top-level continuation β, we observe the following evaluation:
We have successfully evaluated the first term to the result β6β and the second term to the result β10β.
In the following, we will often leave out the first reduction step in examples, thus silently replacing the covariable bound by the outermost π-binding with the top-level consumer β.
Here is a bigger problem that we havenβt addressed yet. The evaluation rules in the present section do not allow to evaluate nested expressions like (β2β β β4β) + β5β in Fun or its translation ππΌ. + (ππ½. β (β2β, β4β; π½), β5β; πΌ) in Core. We will discuss this problem and its solution in more detail in Section 3.
Authors:
(1) David Binder, University of TΓΌbingen, Germany;
(2) Marco Tzschentke, University of TΓΌbingen, Germany;
(3) Marius Muller, University of TΓΌbingen, Germany;
(4) Klaus Ostermann, University of TΓΌbingen, Germany.
This paper is