Notes (TILC 03/05): Conditionals

3 Conditionals

true = \x y.x
false = \x y.y

Why these expressions? Probably because it’s convenient to have false be the same expression as 0_:

false => \x y. y => \s z. z => 0_

This matches the common convention found in imperative programming languages. We could choose to make true equal zero instead, and then change all the logic functions by negating their inputs, so (and a b) in the usual way becomes

(and (not a) (not b))

But I’m not sure if there’s any tangible benefit to doing this other than being contrarian.

3.1 Logical operations

and = \x y. x y (\x y. y) => \x y. x y false

The first parameter of and returns the second parameter, if it is true and false if it is false. The only way And returns true is if both parameters are true.

or = \x y. x (\x y. x) y = \x y. x true y

Same general idea as And. The first parameter selects true if true and y if false.

not = \x. x false true

Straightforward, it’s just flipping the order of the parameter.

3.2 A conditional test

isZero = \x. x false not false

If x is 0_:

isZero 0_ => (\x. x false not false) 0_ =>
0_ false not false =>
not false =>

If x is 1_:

isZero 1_ => (\x. x false not false) 1_ =>
1_ false not false =>
(\x y. y) not false =>
(\y. y) false =>

If x is 2_:

isZero 2_ => (\x. x false not false) 2_ =>
2_ false not false =>
(\x y. y) ((\x y. y) not) false =>
(\y. y) false =>

It doesn’t matter how many times we apply false to not because false applied to any expression is always the Identity function.

3.3 The predecessor function

The predecessor function pred is like the opposite of the successor function succ. Instead of adding one to a number, it subtracts one from a number (the words increment and decrement are sometimes used in other contexts to describe adding or subtracting one, respectively).

Since numbers are functions that take other functions (higher-order functions), and apply them a certain number of times, predecessor can be thought of as an “apply one less time than this number” function.

Recall that succ is

succ = \n f x. f (n f x)

Naively, we might want something that looks like this

pred n = \n f x. (invert f) (n f x)

where (invert f) is the inverse function of f:

(invert f) (f x) = x

But we have a problem: Not every function is invertible.

Take false for example:

false x = (\x y. y) x = (\y. y) = id

Since false of any argument x is the identity function, and since a function can only have one ouput for any given input, there’s no way for us to build an expression for (invert false). And that means that we can build an invert function that is total, in that it is defined over all possible inputs.

But it turns out it is possible to build a total pred function, but we have to try another method. Instead of starting from n and working backwards, we’ll start from 0_ and build forwards. If we apply succ n-times to 0_, we get n. So if we apply succ only (n-1)-times to 0_., /e’ll get n - 1 which is pred n.

We’ll start by building an expression that holds a pair of numbers, a_ and b_. Suppose we just put them next to each other:

a_ b_

This might look fine at first, but watch what happens if we give them concrete values:

0_ 0_ => (\s z. z) (\s z. z)
=> \z.z

The numbers evaluate against each other! Let’s add an abstraction to stop the application:

\x. a_ b_

Okay, that stops the application, but only temporarily. If we ever apply this expression to anything, it’ll just reduce back again and throw away the values in the pair.

What we want is some way to access the two individual values in the pair.

\x. x a_ b_

We already have functions which will return only their first or second arguments: true and false. We can use these to select which element of the pair we want:

(\x. x a_ b_) true  = a_
(\x. x a_ b_) false = b_

Great, we’ve got a pair! Now we need a function which we’ll call phi to turn a pair of numbers (a, b) into (a + 1, a)

phi = \p z. z (succ (p true)) (p true)

For example,

(\p z. z (succ (p true)) (p true)) (\x. x 2_ 1_) =>
(\z. z (succ ((\x. x 2_ 1_) T)) ((\x. x 2_ 1_) T))  =>
(\z. z (succ ((\x. x 2_ 1_) T)) ((\x. x 2_ 1_) T))  =>

Now we just need to apply phi to \x. x 0_ 0_, n-times, and the first element will be equal to n and the second will be n - 1:

\n. n phi (\z. z 0_ 0_)

But since this function is supposed to be the predecessor, we really just want the second element:

pred n = \n. n phi (\z. z 0_ 0_) F

3.4 Equality and inequalities

A number, x is greater than or equal to a number y if applying predecessor x-times to y is 0:

gte x y = \x y. isZero (x pred y)

The reason this function is “greater than or equal to” and not just “equals” is that the predecessor of 0 is 0.

But we can get an equals function by simply doing:

eq x y = \x y. and (gte x y) (gte y x)

Then, not equal is:

neq x y = \x y. not (eq x y)

Greater than:

gth x y = \x y. and (gte x y) (not x y)

Less than or equal to:

lte x y = \x y. not (gth x y)

Less than:

lth x y = \x y. not (gte x y)