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:

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; }

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
yet introduced
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.[3]

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 : cuberoot(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.

1.1.7 Example: Square Roots by Newtons Method