Simply about a century previously, Alonzo Church invented the easy, dapper, and yet elusive lambda calculus. Alongside with Alan Turing, he then proved the Church-Turing thesis: that the rest computable with a Turing machine would possibly per chance perchance even furthermore be computed in the lambda calculus. Nonetheless, virtually as soon as we had digital pc systems, we started inventing programming languages, and with them a predominant treasure of aspects, brilliant and awful, many of which appear very laborious to expose to the typical nature of computability, now not to declare the lambda calculus particularly.

While it’s prison that the rest which is in a position to be computed, duration, would possibly per chance perchance even be computed in the lambda calculus, that you just would possibly per chance perchance also now not want to: it’s austere, to assert the least, and was once now not designed with new sensibilities regarding readability in solutions. We developed all these languages and aspects for a motive! Gentle, Church demonstrated now not most attention-grabbing that it was once imaginable to compute the rest computable with the lambda calculus, but also how one would possibly per chance perchance even enact so.

On this assortment, we’ll peep some ideas to express well-liked programming language aspects the employ of the minimalistic instruments of the lambda calculus. We start up with per chance essentially the most ubiquitous fashion: booleans.

λ is blind

The lambda calculus’s austerity is coarse: you don’t even hold booleans. All you hold are:

  1. Lambda abstractions;

  2. Applications; and

  3. Variables.

We’ll now overview these in some component; truly be at liberty to skip this fragment if you’re already conversant in the lambda calculus.

Lambda abstractions

Lambda abstractions (“lambdas,” “abstractions,” and “options” can even be conventional interchangeably) introduce a purpose of a single variable.

Abstractions are written λ x . y, for variable x and expression y, where x is now accessible as a sure variable in the physique, and any enclosing definition of x is shadowed (i.e. λ x . λ x . x = λ x . λ y . yλ x . λ y . x). (We shall take hold of strictly lexical scoping in the intervening time.)

In Haskell, we would write x -> y as a replace; in JavaScript, purpose (x) { return y } or (x) => y.

Applications

Applications (“purpose application” and “purpose call” will be conventional interchangeably) observe the end outcomes of the expression on the left to the expression on the magnificent.

Applications are written as x y, for expressions x and y, and left-related, i.e. a b c = (a b) ca (b c). Characteristic application binds tighter than lambda abstraction, i.e. λ x . λ y . y x = λ x . λ y . (y x)λ x . (λ y . y) x.

The syntax is the the same in Haskell; in JavaScript, we would write x(y) or a(b, c). Camouflage nevertheless that since lambda calculus options are all single-argument options, a more dispute (even when much less idiomatic) equivalent for the latter would be a(b)(c).

Variables

Variables introduced by enclosing lambdas.

Variable are written as roughly arbitrary names, in general alphanumeric (e.g. x or y0 or component); nevertheless, we can truly be at liberty to incorporate non-alphanumeric characters in names as we see match, since the paucity of syntax manner there’s small possibility of ambiguity.

For the reason that finest accessible variables are these sure by enclosing lambdas, we would possibly per chance perchance even infer that there are now not any let bindings for local variables, and no globals of any fashion; the lambda calculus doesn’t attain with an customary library.

Summary

In quasi-BNF, the grammar for the lambda calculus is extremely minimal:

e := λ x . e | e e | x | (e)

And at final, this table affords a facet-by-facet comparability of the syntax of the lambda calculus with the corresponding syntax in Haskell & JavaScript:

Syntax of the lambda calculus, Haskell, & JavaScript
Lambda calculus Haskell JavaScript
Abstraction λ x . y x -> y (x) => y
Utility f x f x f(x)
Variable x x x

Unconditional λ

Lambdas are the correct manner to introduce values—they’re the correct “literal” syntax in the language. We can attributable to this fact infer that the correct kinds of runtime values must be closures. In an interpreter for the lambda calculus, closures would possibly per chance perchance even encompass the identify of the introduced variable, the physique of the lambda, & a draw about the names and values of any variables it closed over when constructed (once more, we take hold of strict lexical scoping). There are now not any bits, bytes, words, pointers, or objects in the language’s semantics; finest this runtime representation of lambdas.

Likewise, lambdas are also the correct manner to introduce variables—there’s no well-liked library, built-ins, primitives, prelude, or world ambiance to provide well-liked definitions. We’re truly baking the apple pie from scratch.

All of this raises the request: how enact you enact the rest in the event you don’t even hold prison and faux? Lambdas and variables don’t enact, they merely are, so as that leaves application. When all you hold is application, the total lot appears to be like treasure a lambda abstraction, so we’ll represent booleans the employ of lambdas.

No doubt, it’s now not most attention-grabbing booleans we’re after; prison and faux aren’t noteworthy employ without and, or, now not, if, and your total relaxation. To be good, our representation of booleans must peaceful attributable to this fact suffice to account for these, to boot. But how enact you account for if without the employ of if? In a sluggish language treasure Haskell, we would possibly per chance perchance even account for if as a purpose something treasure so:

if_ ::  Bool -> a -> a -> a
if_ cond then_ else_ = if cond then then_ else else_

In a strict language treasure JavaScript, we’d as a replace take options for the picks:

purpose if_(cond, then_, else_) {
  if (cond) {
    then_();
  } else {
    else_();
  }
}

Each these definitions employ the language’s native booleans and if syntax (a tactic for imposing embedded DSLs identified as “meta-circularity”), and thus aren’t viable in the lambda calculus. Nonetheless, they enact give us a touch: in both cases we now hold a purpose taking a condition, consequence, and various, and the employ of the first to take hold of considered one of the latter two. In the lambda calculus, we would possibly per chance perchance even start up by writing:

if = λ cond then else . ?

(Camouflage: there aren’t any keywords in the lambda calculus, so there’s nothing stopping me from naming variables issues treasure if, a fact which I will take free most attention-grabbing thing about.)

We’ve introduced a definition for if, as a purpose of three parameters; now what’s going to we enact with them? The lambda calculus’s stark palette makes it easy to enumerate all the issues we can enact with some variable a:

  1. Ignore it, whether by simply now not declaring it in any admire (as in λ a . λ b . b), or by shadowing it with one other lambda which binds the the same identify (as in λ a . λ a . a).

  2. Unusual it, whether on its remember in the physique of a lambda (as in λ a . a or λ a . λ b . a), someplace inner both facet of an application (as in λ a . λ b . a b or λ a . λ b . b a), or some mixture of both (as in λ a . (λ b . a) a).

We would possibly per chance perchance even as an example simply return then or else:

if = λ cond then else . then
if = λ cond then else . else

But in that case the conditional isn’t conditional in any admire—the value in no manner depends on cond. Clearly the physique must earn employ of all three variables if we desire it to behave treasure the ifs all of us know and cherish from other languages.

Taking a step assist for a moment, let’s peep the roles of if’s arguments. then and else are passive; we finest want to make employ of or protect in solutions one or the opposite reckoning on the value of cond. cond, then, is largely the most essential: it takes the active purpose.

Thus, in the the same manner that our if_ options in Haskell & JavaScript employed these language’s aspects to place into effect, we’re going to account for if cond then else as the application of the condition to the opposite two parameters:

if = λ cond then else . cond then else

This feels surprisingly treasure dishonest: for sure we’ve finest moved the realm around. Now reasonably than if making the resolution about which argument to attain, we’ve deferred it to cond. But if and cond aren’t the the same, semantically; if takes a boolean and two other arguments and returns considered one of the latter, while cond is a boolean—albeit evidently a boolean represented as a purpose. Let’s earn that true by writing down if’s fashion:

if : Bool -> a -> a -> a

Notwithstanding our employ of the yet-to-be-outlined identify Bool for the earn of the condition, right here is the the same fashion as we gave if_ in Haskell; that’s a factual mark that we’re on the magnificent observe! It takes a Bool and two arguments of fashion a, and it must return a vogue of on myth of that’s the correct manner for it to attain assist up with the a that it returns. But what is Bool?

Working backwards from the fashion and definition of if, we see that cond is utilized to 2 arguments, and attributable to this fact must be a purpose of two parameters. Extra, these are both of fashion a, and the value it returns must also be of fashion a for if’s fashion to withhold. Thus, we can account for the fashion Bool treasure so:

Bool = ∀ a . a -> a -> a

If a given Bool is a purpose of two arguments of arbitrary fashion, returning the the same fashion, it must attributable to this fact bag considered one of its arguments to attain. There are finest two distinguishable inhabitants of Bool, prison and faux, so we can attributable to this fact deduce that since if defers the amount of the final result to the Bool, for prison and faux to in point of fact vary they hold to earn reverse picks. In other words, prison must return the then parameter, while faux must return the else one:

prison, faux : Bool
prison  = λ then else . then
faux = λ then else . else

We didn’t pass the realm around finally; we solved it. What we observed was once a deeper insight: this encoding of booleans makes if redundant, since if we can observe if to a Bool and two arguments, we would possibly per chance perchance even equally observe the Bool to those arguments without extend.

It’s in most cases helpful to conflate booleans with bits, their minimal representation, but for sure they’re now not the the same in any admire. Practically, some programming languages account for booleans as a byte in memory, per chance clamping its values to 0 and 1; others account for them as cases of some boolean class, or constructors of an algebraic datatype. Some provide no formal relationship between prison and faux in any admire, keep for a popular interface—duck typing.

Mathematically, booleans are the values in propositional good judgment; the upper and lower bounds of a lattice; the zero and considered one of a semiring; the participants of the location with cardinality 2; and loads of other issues in many varied contexts.

Operationally, booleans represent desire, and right here is a pattern that we’ll see repeated: encoding a datatype with lambdas manner representing the datatype as options supporting all of its operations. All operations on booleans would possibly per chance perchance even be outlined by deciding on between two picks, which is precisely what our encoding does.

We can reward this by defining some other operations on booleans, e.g. logical operators, the employ of the encoding we’ve built to this level.

now not takes a single Bool and returns one other:

now not : Bool -> Bool
now not = λ x . ?

As when defining if, all we can enact with a Bool is branch on it:

now not = λ x . if x ? ?

But which arguments must peaceful we slide if we treasure to attain a Bool with the reverse ticket? Select the definition of Bool from above:

Bool = ∀ a . a -> a -> a

To attain a Bool, attributable to this fact, every argument must likewise be a Bool. The main argument will be selected if x is prison, the second if x is faux, so if we desire the reverse ticket from x we can simply observe it to the reverse values in both purpose:

now not = λ x . if x faux prison

now not x will attributable to this fact return faux if x is prison, and prison if x is faux; equationally:

now not prison  = faux
now not faux = prison

Which is precisely the meaning we intended now not to hold.

or and and are carefully related to every other, so we’ll account for them simultaneously. Each take two Bools and return a Bool:

or, and : Bool -> Bool -> Bool
or  = λ x y . ?
and = λ x y . ?

As with now not, all we can enact with Bools is branch:

or  = λ x y . if x ? ?
and = λ x y . if x ? ?

For or, if x is prison, we can return prison straight (“short-circuiting”). For and, it’s the reverse:

or  = λ x y . if x prison ?
and = λ x y . if x ?    faux

If x is faux, or wants to check whether y is prison; likewise, if x is prison, and wants to check whether y is also prison. Over once more, all we can enact with Bools is branch:

or  = λ x y . if x prison       (if y ? ?)
and = λ x y . if x (if y ? ?) faux

And since we must return a Bool, we can employ prison and faux:

or  = λ x y . if x prison              (if y prison faux)
and = λ x y . if x (if y prison faux) faux

Pleasantly, if y prison faux (and likewise y prison faux) is operationally equivalent to y. The utilization of that equivalence, we can simplify these definitions, leaving us with:

or  = λ x y . if x prison y
and = λ x y . if x y    faux

Conclusion

On this put up, we’ve explored defining a ubiquitous programming language feature—booleans—the employ of nothing bigger than the spartan trappings of the lambda calculus. We’ve emerged with a language which is in a position to express now not merely options and their options, but also traditional metaphysical ideas akin to fact.

In the following put up, we’ll see at lambda-encodings of elegance: ML/Haskell-vogue algebraic datatypes.