Functions, as introduced above, are much like ordinary mathematical functions. They specify a value that is determined by one or more parameters. But there is an important difference between mathematical functions and computer functions. Computer functions must be effective.

As a case in point, consider the problem of computing square roots. We can define the square-root function as \[ \sqrt{x} =\text{ the }y\text{ such that }y \geq 0\text{ and } y^2 = x \] This describes a perfectly legitimate mathematical function. We could use it to recognize whether one number is the square root of another, or to derive facts about square roots in general. On the other hand, the definition does not describe a computer function. Indeed, it tells us almost nothing about how to actually find the square root of a given number. It will not help matters to rephrase this definition in pseudo-JavaScript:

function sqrt(x) { return the y with y >= 0 && square(y) === x; }

The contrast between mathematical function and computer function is a reflection of the general distinction between describing properties of things and describing how to do things, or, as it is sometimes referred to, the distinction between declarative knowledge and imperative knowledge. In mathematics we are usually concerned with declarative (what is) descriptions, whereas in computer science we are usually concerned with imperative (how to) descriptions.[1]

How does one compute square roots? The most common way is to use Newton's method of successive approximations, which says that whenever we have a guess $y$ for the value of the square root of a number $x$, we can perform a simple manipulation to get a better guess (one closer to the actual square root) by averaging $y$ with $x/y$.[2] For example, we can compute the square root of 2 as follows. Suppose our initial guess is 1: \[ \begin{array}{lll} \textrm{Guess} & \textrm{Quotient} & \textrm{Average}\\[1em] 1 & {\displaystyle \frac{2}{1} = 2} & {\displaystyle \frac{(2+1)}{2} = 1.5} \\[1em] 1.5 & {\displaystyle \frac{2}{1.5} = 1.3333} & {\displaystyle \frac{(1.3333+1.5)}{2} = 1.4167} \\[1em] 1.4167 & {\displaystyle \frac{2}{1.4167} = 1.4118} & {\displaystyle \frac{(1.4167+1.4118)}{2} = 1.4142} \\[1em] 1.4142 & \ldots & \ldots \end{array} \] Continuing this process, we obtain better and better approximations to the square root.

Now let's formalize the process in terms of functions. We start with a value for the radicand (the number whose square root we are trying to compute) and a value for the guess. If the guess is good enough for our purposes, we are done; if not, we must repeat the process with an improved guess. We write this basic strategy as a function:

function sqrt_iter(guess, x) { return good_enough(guess, x) ? guess : sqrt_iter(improve(guess, x), x); }

A guess is improved by averaging it with the quotient of the radicand and the old guess:

function improve(guess, x) { return average(guess, x / guess); }

function average(x,y) { return (x + y) / 2; }

We also have to say what we mean by good enough.

The
following will do for illustration, but it is not really a very good
test. (See exercise 1.7.)
The idea is to improve the answer until it is close enough so that its
square differs from the radicand by less than a predetermined
tolerance (here 0.001):

function good_enough(guess, x) { return abs(square(guess) - x) < 0.001; }

Finally, we need a way to get started. For instance, we can always guess that the square root of any number is 1:[3]

function sqrt(x) { return sqrt_iter(1, x); }

If we type these
declarations
to the interpreter, we can use `sqrt`
just as we can use any
function:

sqrt(9);

sqrt(100 + 37);

sqrt(sqrt(2) + sqrt(3));

square(sqrt(1000));

The `sqrt` program also illustrates that the
simple
functional
language we have introduced so far is sufficient for writing any purely
numerical program that one could write in, say, C or Pascal. This might
seem surprising, since we have not included in our language any iterative
(looping) constructs that direct the computer to do something over and over
again.
The function `sqrt_iter`,
on the other hand, demonstrates how iteration can be accomplished using no
special construct other than the ordinary ability to call a
function.[4]

Why can't I just declare an ordinary conditional function whose application works just like conditional expressions?she asks. Alyssa's friend Eva Lu Ator claims this can indeed be done, and she declares a

function conditional(predicate, then_clause, else_clause) { return predicate ? then_clause : else_clause; }

conditional(2 === 3, 0, 5);

conditional(1 === 1, 0, 5);

function sqrt_iter(guess, x) { return conditional(good_enough(guess, x), guess, sqrt_iter(improve(guess, x), x)); }

const error_threshold = 0.01; function good_enough(guess, x) { return relative_error(guess, improve(guess, x)) < error_threshold; } function relative_error(estimate, reference) { return abs(estimate - reference) / reference; }

function good_enough(guess, x) { return abs(cube(guess) - x) < 0.001; } function div3(x, y) { return (x + y) / 3; } function improve(guess, x) { return div3(x / (guess * guess), 2 * guess); } function cube_root(guess, x) { return good_enough(guess, x) ? guess : cube_root(improve(guess, x), x); }

[1]
Declarative and
imperative descriptions are intimately related, as indeed are
mathematics and computer science. For instance, to say that the
answer produced by a program is

correctis to make a declarative statement about the program. There is a large amount of research aimed at establishing techniques for proving that programs are correct, and much of the technical difficulty of this subject has to do with negotiating the transition between imperative statements (from which programs are constructed) and declarative statements (which can be used to deduce things). In a related vein, an important current area in programming-language design is the exploration of so-called very high-level languages, in which one actually programs in terms of declarative statements. The idea is to make interpreters sophisticated enough so that, given

what isknowledge specified by the programmer, they can generate

how toknowledge automatically. This cannot be done in general, but there are important areas where progress has been made. We shall revisit this idea in chapter 4.

[2]
This square-root algorithm is
actually a special case of Newton's method, which is a general
technique for finding roots of equations. The square-root algorithm itself
was developed by Heron of
Alexandria in the first century a.d . We will see how to express
the general Newton's method as a
JavaScript function
in section 1.3.4.

[3]
Observe that we express
our initial guess as 1.0 rather than 1. This would not make any difference
in many Lisp implementations.
MIT Scheme, however, distinguishes between exact integers and decimal values,
and dividing two integers produces a rational number rather than a decimal.
For example, dividing 10 by 6 yields 5/3, while dividing 10.0 by 6.0 yields
1.6666666666666667. (We will learn how to implement arithmetic on rational
numbers in section 2.1.1.) If we start with an
initial guess of 1 in our square-root program, and
$x$ is an exact integer, all subsequent values
produced in the square-root computation will be rational numbers rather than
decimals. Mixed operations on rational numbers and decimals always yield
decimals, so starting with an initial guess of 1.0 forces all subsequent
values to be decimals.

1.1.7 Example: Square Roots by Newton s Method