Imagine a language in which we cannot use names to refer to computational objects. As we write more and more complex programs, keeping track of the details of each step of computation would get increasingly difficult and inconvenient. Thus, we assign values, which are the computational object, to variables, identified by a name, by using define
. This is Racket's simplest means of abstraction.
Though you have seen various variable and procedure definitions scattered throughout the previous sections, we have not yet formally taught how to use define
.
To start off, here are a few example expressions that use define
. Try these out in the Racket interpreter to see what they do.
-> (define x 5)
-> (define (square x) (* x x))
-> (define y (square 3))
-> (define z (+ x y))
The general form of a variable definition is as follows:
(define [name] [value])
The [name]
represents a variable to which values are assigned to. For example:
-> (define x 5)
-> x
5
x
is a variable, and 5
is its value.
[value]
can be replaced with any type of value, even expressions. An important property of variable definitions is that the value of the definition is completely evaluated before being assigned to its variable.
-> (define x (+ 5 5))
-> x
10
Why is x
not (+ 5 5)
? Because when we define x
, we must first evaluate the expression (+ 5 5)
to its simplest form, 10
. We then assign 10
to x
.
(define x (/ 1 0))
?
Procedure definition is an even more powerful abstraction technique than variable definition, in which we can give a name to a compound operation and consequently refer to it as a unit. Let's start with a simple example by defining the square
procedure:
(define (square x) (* x x))
We can understand this in the following way:
(define (square x) ( * x x))
To square x, multiply x with x.
The general form of a procedure definition is as follows:
(define ([name] [formal parameters]) [body])
Notice how a significant difference this has from a variable definition is that the name and parameters are bound by parentheses. Recall that, besides quotes, a set of parentheses represents a procedure call. We can translate this by saying: When we call [name]
with [formal parameters]
, we will do [body]
.
foo
that takes in no arguments, and returns 5
?
An important property of procedure definitions is that the body of the procedure is not evaluated until the procedure is called. This means that when we define square
, we do not yet know that we need to multiply x
by itself. Only when we call square
on some number, say 3
, do we know that we have to call (* 3 3)
.
(define (x) (/ 1 0))
?
One thing to pay attention to when creating compound procedures is naming. We need to be very careful when naming our procedures and formal parameters. Racket will not accept multiple definitions, which means that any procedure already defined cannot be used as the name of a compound procedure or a formal parameter. This is an example of a compound procedure definition that is NOT ALLOWED (try it in your interpreter to see why):
(define (foo sent word)
(word sent word))
In both instances in the body, which word
are we referring to, the parameter or the built-in procedure?
Returning to the concept of nesting expressions, we can also nest procedures within other procedure definitions. As you did in Homework 0-1, we can define the procedure sum-of-squares
by using the procedure square
in its definition:
(define (sum-of-squares x y)
(+ (square x) (square y)))
This roughly translates to: When we call sum-of-squares
on x
and y
, we will add the square
of x
to the square
of y
.
To clarify,
(define foo 10)
foo
is the variable name.10
is the value assigned to foo
.(define (square x) (* x x))
square
is the procedure name.x
is its only formal parameter.(* x x)
is its body.-> (square 3)
9
square
is the procedure, and is the operator of this expression.3
is the argument to square
, and is the operand of this expression.9
is the return value.define
as a means of abstractionThe possibility of associating values and operations to symbols and later retrieving them means that the Racket interpreter must have some form of memory to keep track of these associative pairs. We call this memory the environment, which we will expand more on in Lesson 8.