Prev Index Next

Procedures

Previously, I overviewed two important concepts: expressions and evaluation. While learning Scheme, often called a functional language, I thought that functions must be very important (even more so given their unique name: procedures). This is why I wanted, next, to know more about procedures and procedure calls.

A Scheme procedure is like a function in languages like C, but more powerful, as it can be created and used at will during the program’s evaluation. Procedures are created by writing lambda expressions and are used by writing procedure call expressions.

Let us see how to create procedures.

Lambda the ultimate

Scheme procedures are created by writing lambda expressions, a special form of parenthesized expressions that always begin with the lambda identifier. Here is an example of a procedure that accepts no arguments and returns unity:

> (lambda () 1)
$1 = #<procedure 55e262177b80 at <unknown port>:1:0 ()>

Ignore the 55e2..., which is just Guile saying that this is a procedure that accepts no arguments.

We say that a lambda expression evaluates into a procedure. This is very important.

Each expression in a computer program is evaluated to determine its meaning. To run a computer program simply means to read and evaluate expressions. But lambda expressions are special. They evaluate into procedures, while the program is running. In fact, we say that procedures are first-class citiziens in Scheme, which means they are like any other data and can be used as arguments, returned from other procedures, and created at runtime.

I was blown away by this fact, the fact that procedures are simply evaluated lambda expressions. A procedure is a concept or a name given to evaluation of lambda expressions. This is one of the reasons why I named this heading: Lambda The Ultimate.

Here is one of the simplest procedures: the identity procedure, which returns its argument:

> (lambda (x) x)
$2 = #<procedure 55e26217b728 at <unknown port>:2:0 (x)>

You can create procedures that accept more arguments. Here is an example:

> (lambda (x y z) x)
$3 = #<procedure 55e26217e2b8 at <unknown port>:3:0 (x y z)>

In r5rs Scheme, the lambda identifier has three forms depending on the number of arguments:

  1. (lambda (<arg-1> ...) <exp-1> ...) – fixed number of arguments;
  2. (lambda <arg> <exp-1> ...) – any number of arguments converted as a list object, described later (after the chapter on lists);
  3. (lambda (<arg-1> ... <arg-n> . <args>) <exp-1> ...) – at least n arguments.

Here are examples for each case:

> (lambda (x) x)
$4 = #<procedure 55ccc97e7940 at <unknown port>:4:0 (x)>
> (lambda x x)
$5 = #<procedure 55ccc97eba60 at <unknown port>:5:0 x>
> (lambda (x y . z) x)
$6 = #<procedure 55ccc97eddc8 at <unknown port>:6:0 (x y . z)>

In Guile Scheme, the lambda* identifier supports optional and keyword arguments.

Here is an example of a procedure with optional arguments y, z, and w:

> (lambda* (x #:optional y z . w) x)
$7 = #<procedure 55e262187260 at <unknown port>:7:0 (x #:optional y z . w)>

Here is an example of a procedure with keyword argument y:

> (lambda* (x #:key y) y)
$8 = #<procedure 55e2621850b8 at <unknown port>:8:0 (x #:key y)>

Optional and keyword arguments can have default values:

 > (lambda* (x #:optional (y 2)) y)
$9 = #<procedure 55e262199120 at <unknown port>:9:0 (x #:optional y)>

Let us now make use of procedures by calling them.

Procedure calls

A lambda expression evaluates into a procedure. Let us see how to make use of procedures.

A lambda expression is written within () parentheses and begins with the lambda identifier. But by writing the expression inside of a new pair of parentheses, a new expression type is created, called a procedure call. Here is an example.

> ((lambda () 1))
$1 = 1

The example above has two expressions. The first expression is the lambda expression, which evaluates into a procedure. The second expression is a procedure call.

Here is the example of a procedure call of the identity procedure.

> ((lambda (x) x) 1)
$2 = 1 

The identity procedure accepts one argument, denoted by x, and returns this argument unchanged. The procedure call expression now contains another element: the argument for the procedure. The evaluator returns the result of evaluating the procedure with the corresponding arguments. The number of (required) procedure arguments must match the number of elements of the procedure call (not including the lambda identifier).

In general, a procedure call is an expression in the form of (<operator> <operand-1> ...). The operator is always a procedure, an evaluated lambda expression. This is called prefix notation, and simplifies the rules. There is no confusion on which operator is applied first, unlike in C or Python, where the language forces you to learn rules.

A procedure call with keyword arguments has special syntax

> ((lambda* (x #:key y) y) 1 #:y 2)
$3 = 2

You can try to cheat Guile by telling it y is an optional/key argument which is returned, yet never provide it in the procedure call.

> ((lambda* (x #:optional y) y) 1)
$4 = #f
> ((lambda* (x #:key y) y) 1)
$5 = #f

The value #f means false. The antonym of false is true, which has its own value: #t.

It is often that you create procedures in Scheme, which is why naming them is important when writing programs.

Naming procedures

Procedures are named by writing define expressions, a special form of parenthesized expressions that always begin with the define identifier. Here is an example of a procedure, named unity, that accepts no arguments and returns unity:

> (define (unity) 1)

Guile returns no result after evaluating the expression above. To check what happened, simply write unity as an expression:

> unity
$1 = #<procedure unity ()>

Recall that evaluation has two kinds of results: the value of the evaluated expression, and the side effects. A define expression has no return value, but instead it has a side effect of binding a value to an identifier.

Calling the procedure above is simple:

> (unity)
$2 = 1

You can also define variables like:

> (define unity 1)
> unity
$3 = 1

Here are two identical examples of the identity procedure:

> (define (identity x) x)
> (define identity (lambda (x) x))

In fact, the expression (define (identity x) x) is just syntactic sugar.

Prev Index Next