Programming Language Design 2

Reduction and Evaluation


Learning Objectives

  • You know what a value is in lambda calculus.
  • You know the evaluation rules of lambda calculus, and 𝛽-reduction.
  • You can extend lambda calculus with simple constructions such as let expressions.

Here, we focus on the heart of lambda calculus called reduction. Specifically, we talk about 𝛽-reduction, which encodes computation.

Other reductions have also been studied. We briefly mentioned 𝛼-reduction in the previous chapter, but 𝛼-reduction is not used for the dynamics of programming languages, instead, 𝛽-reduction is.

Reduction

Reduction is a process which turns lambda terms to other lambda terms maintaining the meaning of the term in the process. 𝛽-reduction is the process of reducing a lambda term step by step towards some value.

Values

The concept of a value is different from part 4, where values had a distinct data type. Instead, in lambda calculus, being a value is just a property of a term. For simplicity, we consider that only an abstraction is a value of lambda calculus, whether or not its body is a value (or even whether it’s complete or not).

Definition 6.4.1: ValueThe set of values ValueβŠ‚Term is defined as {𝑑:Term|is_value𝑑},where is_value is defined as follows.is_value:Termβ†’{0,1}is_valueAbs(var:_,body:_)=1is_value_=0We often refer to values using the variable name 𝑣.

Let’s now invent a notation for 𝛽-reduction. If the term 𝑑1 reduces to the term 𝑑2 we write 𝑑1βŸΆπ‘‘2.

Here are some examples for 𝛽-reduction of lambda terms.

  1. (πœ†π‘₯.π‘₯)id⟢id
  2. ((πœ†π‘₯.πœ†π‘¦.𝑦)id)id⟢(πœ†π‘¦.𝑦)id⟢id where id=πœ†π‘₯.π‘₯

We also decide that variables should not reduce to anything, and instead should lead to an error. This is because only complete programs should be evaluated in the first place as complete programs remain complete under 𝛽-reduction.

We will use the informal lambda calculus notation, but the reader is expected to understand it as just β€œsyntax sugar” for the formal lambda terms defined earlier.

Here is a reminder of the formal definition of lambda terms:

Term≔Var(String)|Abs(var:String,body:Term)|App(Term,Term)

As with any relation, we can express 𝛽-reduction using a set of inference rules. 𝛽-reduction is defined as the smallest closure of the following rules:

𝑑1βŸΆπ‘‘β€²1App1𝑑1 𝑑2βŸΆπ‘‘β€²1 𝑑2𝑑2βŸΆπ‘‘β€²2is_value(𝑣1)App2𝑣1 𝑑2βŸΆπ‘£1 𝑑′2is_value(𝑣2)AppAbs(πœ†π‘₯.𝑑1) 𝑣2⟢[π‘₯↦𝑣2]𝑑1

As usual, we tacitly assume that 𝑑1,𝑑′1,𝑑2,𝑑′2,𝑣1,𝑣2 are lambda terms and π‘₯ is a string.

The purpose of the App1 rule is to reduce the β€œfunction term” until it is a value, and App2 reduces the argument until it’s a value. Once the function term has been (hopefully) reduced to an abstraction and the argument to a value, AppAbs substitutes the variable parameter by the value 𝑣2 inside the function body 𝑑1. These rules a written in a way that ensures there’s at most one term that a term can reduce to. Notably this requires that values don’t evaluate to anything, because otherwise App1 could be used with 𝑑1 and 𝑑′1 the same value.

βˆ€π‘‘,Β¬is_valueπ‘‘β‡’βˆƒ! 𝑑′,π‘‘βŸΆπ‘‘β€²

The notation βˆƒ! stands for exists unique.

Definition 6.4.2: 𝛽-reductionThe evaluation relation (⟢) between two terms is the smallest relationsuch that the following holds.Let 𝑑1,𝑑′1,𝑑2,𝑑′2,𝑣1,𝑣2 be lambda terms and π‘₯ be a string.1.If 𝑑1βŸΆπ‘‘β€²1, then 𝑑1𝑑2βŸΆπ‘‘β€²1𝑑2.2.If 𝑣1 value, and 𝑑2βŸΆπ‘‘β€²2, then 𝑑1𝑑2βŸΆπ‘‘1𝑑′2.3.If 𝑣2 value, then (πœ†π‘₯.𝑑1) 𝑣2⟢[π‘₯↦𝑣2]𝑑1.Where substitution ([π‘₯↦𝑣]𝑑) is defined as in Definition 6.3.5.

Note that we give no rules for reducing values. This is because a term should not be reduced after it has reached its final value.

Loading Exercise...
Loading Exercise...
Loading Exercise...
Loading Exercise...

Small-step Semantics

The semantics, or more precisely, the dynamics of lambda calculus are described in small-step style, which means that evaluation works one step at a time by doing 𝛽-reduction repeatedly.

Using the 𝛽-reduction defined above (along with substitution), we are ready to implement the step function. This is the topic of the next exercise.

Loading Exercise...

Multi-Step Evaluation

Definition 6.4.3: Multi-Step RelationDefine π‘‘βŸΆβˆ—π‘£ to denote that the Term 𝑑 reduces to the value 𝑣 in somenumber of steps.π‘‘βŸΆπ‘‘β€²βŸΆπ‘‘β€³βŸΆβ‹…β‹…β‹…βŸΆπ‘£

The big-step evaluation 𝑑⇓𝑣 is typically equivalent to π‘‘βŸΆβˆ—π‘£.

Next, implement multistep which applies step repeatedly until reaching a value.

Loading Exercise...

Base Types

So far, we haven’t seen any base types, such as integers or booleans in the lambda calculus, so let’s add them next.

In theory, the theory of integers and booleans is expressible in plain untyped lambda calculus with simply adding syntax sugar for representing them. However, like parsing directly to AST, we can just work and reason with the sugared language and skip the translation/lowering process entirely. This way we need to write more code for type checking and evaluation, but the features of the language are immediately expressible without encoding and decoding. Also then the evaluated terms actually resemble booleans etc. rather than some hard-to-read encoded versions making a REPL for the language more usable.

We will not actually implement booleans in this chapter, but in the next chapter instead.

Let Expressions

Let’s add let expressions, like letπ‘₯=𝑦inπ‘₯, to the language.

A note about the syntax: changing in to be ; or even just a new line would make the syntax look much more imperative while still being a functional language. Consider trying different variations in your own project.

First, we need to extend the set (type) of terms to include let expressions:

Term⩴…|Let(var:String,val_t:Term,body:Term)

The name val_t is called the β€œvalue” assigned to the variable var, even though it might not actually be a value and is reducible. The evaluation needs to reduce it to a value before substituting in the body.

Now, substitution needs to be changed. Let expressions should have similar semantics as abstractions, so we only substitute in the body if the variables don’t match.

subst:(String,Term,Term)β†’Termsubstπ‘₯ 𝑣Var(π‘₯)=𝑣substπ‘₯ 𝑣Var(𝑦)=Var(𝑦),ifπ‘₯≠𝑦substπ‘₯ 𝑣App(𝑑1,𝑑2)=App(subst(π‘₯,𝑣,𝑑1),subst(π‘₯,𝑣,𝑑2))substπ‘₯ 𝑣Abs(π‘₯,𝑇,𝑑)=Abs(π‘₯,𝑇,𝑑)substπ‘₯ 𝑣Abs(𝑦,𝑇,𝑑)=Abs(𝑦,𝑇,subst(π‘₯,𝑣,𝑑))ifπ‘₯≠𝑦substπ‘₯ 𝑣Let(π‘₯,𝑑1,𝑑2)=Let(π‘₯,subst(π‘₯,𝑣,𝑑1),𝑑2)substπ‘₯ 𝑣Let(𝑦,𝑑1,𝑑2)=Let(𝑦,subst(π‘₯,𝑣,𝑑1),subst(π‘₯,𝑣,𝑑2))ifπ‘₯≠𝑦

Also, we consider let expressions not values as intuitively we know they can always be reduced.

Evaluation

Evaluation of let expressions works by first reducing the val_t to a value, and then substituting the variable var by that value in the body.

This can be expressed using the following rules:

𝑑1βŸΆπ‘‘β€²1Let1letπ‘₯=𝑑1inπ‘‘βŸΆletπ‘₯=𝑑′1in𝑑is_value(𝑣1)Letletπ‘₯=𝑣1inπ‘‘βŸΆ[π‘₯↦𝑣1]𝑑
Loading Exercise...