Low-Level Functional Programming

How to compute the length of a list?

Recall fundamental list-dichotomy

Introducing the rewrite symbol:

=>
 

Rule for the empty list:

length( [ ] ) => 0;
 
 

Rule for the non-empty list:

length( [F | R] ) => length(R) + 1;
 
 

The two rules comprise an inductive definition:

The first rule is the basis.

length( [ ] ) => 0;

 

The second is an induction rule.

length( [F | R] ) => length(R) + 1;

 

Sample computation by rewriting:

length([2, 3, 5, 7])

=> (length([3, 5, 7]) + 1)

=> ((length([5, 7]) + 1) + 1)

=> (((length([7]) + 1) + 1) + 1)

=> ((((length([ ]) + 1) + 1) + 1) + 1)

=> (((( 0 + 1) + 1) + 1) + 1)

=> ((( 1 + 1) + 1) + 1)

=> (( 2 + 1) + 1)

=> ( 3 + 1)

=> 4


The symbol

==>

is the transitive closure of relation =>, meaning:

 

If there are terms T1, T2, ...., Tn such that

T1 => T2 => .... => Tn

then we write

T1 ==> Tn

 

For example,

length([2, 3, 5, 7]) ==> 4

 

Although it often is,the thing on the right of ==> need not be the final result. For example, the following is valid:

 

(length([3, 5, 7]) + 1) ==> (((length([7])+ 1) + 1) + 1)


Appending one list to another, e.g.

append([1, 2, 3], [4, 5]) ==> [1, 2, 3, 4, 5]

 

What rules could we use?

 

Ideally chose only one of the arguments as the induction variable.

Hindsight will show us that the first argument is the right one for append. It is not obvious at first.

append( [ ], M ) => M;

is clearly correct.

Also:

append( [A | L], M ) => [A | append(L, M)];

 

For example, take

A = 1, L = [2, 3], M = [4, 5].

Then lhs is

append( [1, 2, 3], [4, 5] )

while rhs is

[1 | append([2, 3], [4, 5])]

Since we expect that

append([2, 3], [4, 5]) ==> [2, 3, 4, 5]

The rhs is equivalent to

[1 | [2, 3, 4, 5] ]

i.e.

[1, 2, 3, 4, 5]

 

The full rewrite sequence is:

append([1, 2, 3], [4, 5])

=> [1 | append([2, 3], [4, 5]) ]

=> [1, 2 | append([3], [4, 5]) ]

=> [1, 2, 3 | append([ ], [4, 5]) ]

=> [1, 2, 3 | [4, 5] ]

=> [1, 2, 3, 4, 5]

Do you notice anything interesting above?


Formal parameters vs. actual parameters

On the lhs, A, L, and M are the formal parameters.

append( [A | L], M ) => [A | append(L, M)];

 

In

append([1, 2, 3], [4, 5])

the corresponding actual parameters are

A == 1

L == [2, 3]

M == [4, 5]


Exercise: Give a set of rules for computing the product of a list of numbers (without using reduce).

 

 

 

 


Exercise: Give a set of rules for a function which computes the list of squares of each of a list of numbers (without using map).

 

 

 

 


Exercise: Using your function days_of_month constructed in the homework, give rules for the function total_days which takes as an argument a list of months and returns the sum of the days in those months.

 

 

 

 


Exercise: Give rules for the function at_least which tells whether a list has at least a certain number of elements. For example:

 

at_least(3, [1, 2, 3]) ==> 1

 

at_least(3, [1, 2]) ==> 0

 

Avoid counting all of the elements of the list.


Exercise: Give rules for a function that produces the list of prime factors of a natural number. For example

 

factors(72) ==> [2, 2, 2, 3, 3]


Lists vs. Numbers

We could repeat the development of append for numbers in place of lists:

0 is [ ]

1 is [o]

2 is [o, o]

3 is [o, o, o]

...

append is like the addition function.

Function successor:

successor(L) => [o | L];

is like the function

(X)=> X+1

 

We can match on numbers as well as lists:

add(0, N) => N;
 
add(M+1, N) => add(M, N) + 1;
 
How to define multiplication?
multiply(0, N) => 0;
 
multiply(M+1, N) => add(multiply(M, N), N);

 

This sort of development, on the domain of natural numbers

{0, 1, 2, 3, ...}

is known as recursive function theory. We could go on and define power, subtraction, conditional operations, etc.


Guarded Rules

Form:

lhs => guard ? body;

 

 

Example: Euclid's Algorithm

gcd: greatest-common-divisor

A related function is

lcm: least-common-multiple

This is what you use to make two fractions have the same denominator:

Consider 1/15 + 1/35
 
lcm(15, 35) == 105
 
1/15 + 1/35 == 7/105 + 3/105 
 
            == 10/105
 

lcm(X, Y) = X*Y / gcd(X, Y)

lcm(15, 35) == 15*35/5

 

Euclid's Algorithm:

gcd(0, Y) => Y;
 
gcd(X, Y) => X <= Y ? gcd(Y-X, X);
 
gcd(X, Y) => gcd(Y, X);
 
 

Rule ordering convention: A rule is applicable only if preceding rules are not.

gcd(18, 24) ==>

gcd(6, 18) ==>

gcd(12, 6) ==>

gcd(6, 12) ==>

gcd(6, 6) ==>

gcd(0, 6) ==>

6


Conditional Expressions

Alternative to two guarded rules.

C ? A : B

Sometimes one is clearer, sometimes the other.

Alternates:

 

gcd(0, Y) => Y;
gcd(X, Y) => X <= Y ? gcd(Y-X, X) : gcd(Y, X);

 

gcd(X, Y) = X == 0 ? Y : gcd(Y-X, X) : gcd(Y, X);


Equational Guards

To avoid re-evaluation:

f(X) => Y = sqrt(X),

g(Y, Y);

instead of

f(X) => g(sqrt(X), sqrt(X));

 

For documentation purposes:

f(X, Y) => First_Vowel = find(vowel, X),

g(First_Vowel, Y);

 

To make a wrapper:

f(X, Y) => X = g(X, Y), X*Y;

^

redefines X locally

 

To decompose a list argument:

f(L) =>

[X, Y, Z] = L,

g(X, Y) + h(Z);


Accumulator-Arguments

reverse([1, 2, 3]) ==> [3, 2, 1]

Give rules for defining reverse:

 


Interface vs. auxiliary functions

 

 


Radix Representation

Normally a numeral such as

4527

means

4*1000 + 5*100 + 2*10 + 7*1

i.e.

4*103 + 5*102 + 2*101 + 7*100

 

Here 10 is called the base or radix of the representation.

Other bases are possible, e.g.

base 8, where 4527 means:

4*256 + 5*64 + 2*8 + 7*1

i.e.

4*83 + 5*82 + 2*81 + 7*80

 

The valid digits in base r range from 0 to r-1.

base 2:

110111

means

1*32 + 1*16 + 0*8 + 1*4 +1*2 +1*1

i.e.

1*25 + 1*24 + 0*23 + 1*22 +1*21 +1*20


How to convert a number to its list of digits in some base?

In general, for base r, numeral

dn-1 dn-2 .... d2 d1 d0

means

dn-1*rn-1 + dn-2 *rn-2 + .... d2 *r2 + d1 *r1 + d0 *r0

 

 

How to find the least significant digit?

We think of this number as:

(dn-1*rn-1 + dn-2 *rn-2 + .... d2 *r2 + d1 *r1) + d0 *r0

which is equal to

(dn-1*rn-2 + dn-2 *rn-3 + .... d2 *r1 + d1 *r0)*r + d0

This number mod r (i.e. the remainder when the number is divided by r) is d0, since the indicated sum is divisible by r. So the last term mod r is d0, the least-significant digit.

 

To find the least significant digit then, just use the number mod r. In rex, mod is represented by %, i.e.

n % r

 

How to find the next significant digit?

Use integer division

n / r

to get the quotient of

(dn-1*rn-2 + dn-2 *rn-3 + .... d2 *r1 + d1 *r0)*r + d0

divided by r. This quotient is:

(dn-1*rn-2 + dn-2 *rn-3 + .... d2 *r1 + d1 *r0)

The same procedure can be applied to that number, and so on, until only 0 is left as the quotient.

 

How might this look in rex? Assume we are interested in radix 2, where the digits are called bits, for example:

 

bits(0) => [0];

bits(N) => [N % 2 | bits(N / 2)];

This produces the digits in reverse order (least-significant digit first), which is ok as long as we expect that.

It also produces a leading 0 for every number, which is not really desirable aesthetically, although it doesn't hurt.

If we let the basis rule be:

bits(0) => [];

this causes a problem for the number 0.

The cleanest way to handle it is to use an interface function:

bits(0) => [0];

bits(N) => bits1(N);

bits1(0) => [];

bits1(N) => [N % 2 | bits1(N / 2)];

 

 

To produce the digits most-significant digit first without using an explicit reverse, one can use an accumulator:

bits(0) => [0];

bits(N) => bits(N, [ ]);

bits(0, Tail) => Tail;

bits(N, Tail) => bits(N/2, [N % 2 | Tail]);

 


How to do arithmetic on arbitrarily-large numbers represented as a list of their digits?

Addition

Multiplication


Review Terms:

actual parameters

append

bit

conditional expression

equational guard

Euclid's Algorithm

formal parameters

fundamental list-dichotomy

induction variable

gcd

lcm

length

mod

natural number

numeral

radix representation

recursive function theory

rewrite

transitive closure

wrapper

Review symbols:

=>

==>

?

:

,

%