The expressive power of the class of
functions
that we can define at this point is very limited, because we have no way to
make tests and to perform different operations depending on the result of a
test.
For instance, we cannot declare a function that computes the
absolute value of a number by testing whether the number is positive or
not, and taking different actions in each case according to the rule
\[
|x| = \left\{ \begin{array}{rl}
x & \mbox{if $x \geq 0$} \\
-x & \mbox{otherwise}
\end{array}
\right.
\]
This construct is a case analysis and can be expressed
in JavaScript using a conditional expression as follows:
function abs(x) {
return x >= 0 ? x : -x;
}
The general form of a conditional expression is
$predicate$ ? $consequent$-$expression$ : $alternative$-$expression$
Conditional expressions begin with a
$\textit{predicate}$—that is,
an expression whose value is interpreted as either
true or false, two distinguished
boolean values in JavaScript.[1]
Note that the primitive boolean expressions
true and
false trivially evaluate
to the boolean values true and false, respectively.
The $\textit{predicate}$
is followed by a question mark, the
$\textit{consequent-expression}$,
a colon, and finally the
$\textit{alternative-expression}$.
To evaluate a conditional expression,
the interpreter starts by evaluating the
$\textit{predicate}$
of the expression. If the
$\textit{predicate}$
evaluates to true, the interpreter evaluates the
$\textit{consequent-expression}$.
Otherwise it evaluates the
$\textit{alternative-expression}$.
The word predicate is used for operators and functions that
return true or false, as well as for expressions that
evaluate to true or false. The absolute-value function
abs makes use of the
primitive predicate >=,
an operator that takes two numbers as operands and tests whether the
first number is greater than or equal to the second number, returning
true or false accordingly.
In addition to primitive predicates such as
>=,
>,
<,
<=, and
===,
there are logical composition operations, which enable us to construct
compound predicates. The three most frequently used are these:
$ \textit{expression}_1$ &&
$\textit{expression}_2$
The interpreter evaluates
$\textit{expression}_1$.
If it evaluates to false, the value of the whole expression is
false, and $\textit{expression}_2$
is not evaluated. If $\textit{expression}_1$
evaluates to true, the value of the whole expressionis the
value of $\textit{expression}_2$.
$\textit{expression}_1$
||
$\textit{expression}_2$
The interpreter evaluates
$\textit{expression}_1$. If it evaluates
to true, the value of the whole expression is true,
and $\textit{expression}_2$ is not
evaluated. If $\textit{expression}_1$
evaluates to false, the value of the whole expression is the
value of $\textit{expression}_2$.
!
$\textit{expression}$
The value of the expression is true when
$\textit{expression}$
evaluates to false, and false otherwise.
Notice that && and
|| are not evaluated like
arithmetic operators such as
+, because their right-hand
expression is not always evaluated. The operator
!, on the other hand,
follows the evaluation rule of section
1.1.3.
It is a unary operator, which means that it takes only
one argument, whereas the arithmetic operators and primitive predicates
encountered so far
are binary, taking two arguments. The operator
! precedes its argument;
we call it a prefix operator. Another prefix operator is
the unary minus operator, an example of
which is the expression -x
of the function abs
in the beginning of this section.
As an example of how these predicates are used, the condition that a
number $x$ be in the range
$5 < x < 10$ may be expressed as
x > 5 && x < 10
Note that the binary operator
&&
has lower precedence than the comparison operators
> and
<.
As another example, we can define a predicate to test whether one
number is greater than or equal to another as
function greater_or_equal(x, y) {
return x > y || x === y;
}
or alternatively as
function greater_or_equal(x, y) {
return ! (x < y);
}
The function greater_or_equal
when applied to two numbers, behaves the same as the operator
>=.
Exercise 1.1
Below is a sequence of
statements.
What is the result printed by the interpreter in response to each
statement?
Assume that the sequence is to be evaluated in the order
in which it is presented.
10;
5 + 3 + 4;
9 - 1;
6 / 2;
2 * 4 + (4 - 6);
const a = 3;
const b = a + 1;
a + b + a * b;
a === b;
b > a && b < a * b
? b : a;
a === 4 ? 6 : b === 4 ? 6 + 7 + a : 25;
2 + (b > a ? b : a);
(a > b
? a
: a < b
? b
: -1)
*
(a + 1);
Note that the statement
a === 4 ? 6 : b === 4 ? 6 + 7 + a : 25;
consists of two conditional expressions, where the second one forms the
alternative of the first one. To make that clear, we usually indent the
lines like this:
a === 4
? 6
: b === 4
? 6 + 7 + a
: 25;
There is currently no solution available for this exercise. This textbook adaptation is a community effort. Do consider contributing by providing a solution for this exercise, using a Pull Request in Github.
Exercise 1.2
Translate the following expression into
JavaScript
\[
\frac{5+4+\left(2-\left(3-(6+\frac{4}{5})\right)\right)}{3 (6-2) (2-7)}
\]
Exercise 1.3
Declare a function
that takes three numbers as arguments and returns
the sum of the squares of the two larger numbers.
function f(x, y, z) {
return square(x) + square(y) + square(z) -
// subtract the square of the smallest
square(x > y ? (y > z ? z : y) : (x > z ? z : x));
}
Exercise 1.4
Observe that our model of evaluation allows for application combinations
whose function expressions are compound expressions. Use this observation
to describe the behavior of the following function:
function plus(a, b) { return a + b; }
function minus(a, b) { return a - b; }
function a_plus_abs_b(a, b) {
return (b >= 0 ? plus : minus)(a, b);
}
According to section 1.1.5, evaluation
of a application expression proceeds as follows:
Evaluate the function expression of the application
combination, resulting in the function to be applied.
Evaluate the argument expressions of the combination.
Evaluate the return expression of the function with each
parameter replaced by the corresponding argument.
Thus the evaluation of the application expression
a_plus_abs_b(5, -4)
(1) evaluates a_plus_abs_b,
resulting in the function given above, and (2) the arguments are
already values. So we need to evaluate (3) the return expression
of the function, with the parameters replaced by the arguments, thus:
(-4 >= 0 ? plus : minus)(5, -4).
With the same rules, we need to (1) evaluate the function expression,
which in this case is the conditional expression
-4 >= 0 ? plus : minus. Since
the predicate evaluates to false, the function expression
evaluates to minus. The arguments,
again (2) are already values. Thus we end up evaluating (3) the body of
minus with the parameters
a and
b replaced by 5 and -4,
respectively, resulting in
5 - (-4), which will finally
evaluate to 9.
Exercise 1.5
Ben Bitdiddle has invented a test to determine whether the interpreter
he is faced with is using applicative-order evaluation or normal-order
evaluation. He
declares the following two functions
:
function p() {
return p();
}
function test(x, y) {
return x === 0 ? 0 : y;
}
Then he evaluates the
statement
test(0, p());
What behavior will Ben observe with an interpreter that uses
applicative-order evaluation? What behavior will he observe with an
interpreter that uses normal-order evaluation? Explain your answer.
(Assume that the evaluation rule for
conditional expressions
is the same whether the interpreter is using normal or applicative order:
The predicate expression is evaluated first, and the result determines
whether to evaluate the consequent or the alternative expression.)
In applicative-order evaluation of
test(0, p()),
we need to evaluate the argument expressions before we can evaluate
the return expression of the function
test.
The evaluation of the argument expression
p()
will not terminate, however: It will keep evaluating application
expressions of the form
p(), and thus the evaluation of
test(0, p()) will not produce a
legitimate value. In normal-order evaluation, on the other hand,
the function application
test(0, p())
would immediately evaluate the return expression of the function
test,
x === 0 ? 0 : y
after replacing the parameter
x with
0 and
y with
p().
The result of the replacing would be
0 === 0 ? 0 : p().
The evaluation of the predicate
0 === 0
results in true and thus the conditional
expression evaluates to 0, without any need to
evaluate p().
[1]
In JavaScript, other values are automatically converted into
true and
false
according to conversion rules, but we choose not to make
use of these conversion rules in this book.