###
Logical Connectives and Conditionals:

Syntactic Abstractions of Core Features

#### Where We Are

We are discussing the idea of *syntactic abstractions*,
those features of a language that are convenient to have but
that are not absolutely essential to the language. At this
point, we have considered how the following common features of
a programming language can, in fact, be considered abstractions
of other, more primitive features: functions that take more than
one argument, local variables, and local functions. This section
considers logical connectives, conditional expressions, and case
analysis.

#### Logical Connectives as Syntactic Abstractions

Like most languages, Racket provides ways to write compound Boolean expressions. For example:

> (and (> 1 0) (< 1 0)) #f > (and (procedure? car) (procedure? cons)) #t > (or (> 1 0) (< 1 0)) #t > (or (procedure? 'car) (procedure? 'cons)) #f > (not (< 1 0)) #t > (not (procedure? cons)) #f

Logical operators such as `and`

and `or`

have
a special semantics. We have already seen that having variable
arity is nothing special, but there is something else. To see
what is special about them, let's consider a function definition
of `and`

using our evaluation model. When we evaluate
a function, we (1) evaluate each of the subexpressions and (2)
apply the leftmost result to the rest.

If `and`

were a function, then it would have to evaluate
all of its arguments. But consider the expression
`(and #f #t #t #t #t #t #t #t)`

. Before the evaluator
applied `and`

, it would have to evaluate seven
expressions to `#t`

in order to determine that the value
of expression is `#f`

. Even if each of these
subexpressions required an expensive function call, it would have
to evaluate all of them *first*.

For this reason, most languages offer logical connectives that do "short-circuit evaluation" of their arguments. As soon as the interpreter encounters a value that determines the value of the whole expression, it returns that value. This means that, in function, the connectives must be special forms, not functions.

We can describe how short-circuit evaluation works with the
inductive definitions for `and`

and `or`

.
These definitions show how to translate one expression into
another, with a simpler version of the operator in question.

`and`

Expressions

An `and`

expression:

(andtest_1test_2...test_n)

is equivalent to an `if`

expression:

(iftest_1(andtest_2...test_n) ;; looking for a reason false)

An `and`

expression with a single argument is equivalent
to that argument.

So, for example, this `and`

expression:

(and (list? arg) (null? (rest arg)))

is equivalent to:

(if (list? arg) (null? (rest arg)) false)

`or`

Expressions

An `or`

expression:

(ortest_1test_2...test_n)

is equivalent to an `if`

expression:

(iftest_1true (ortest_2...test_n))

An `or`

expression with a single argument is equivalent
to that argument, too.

So, for example, this `or`

expression:

(or (list? arg) (null? (rest arg)))

is equivalent to:

(if (list? arg) true (null? (rest arg)))

##### Transational Semantics

These translations show us that `and`

and
`or`

are really syntactic abstractions of the more
general conditional expression `if`

. Indeed, many of
you still avoid use of `and`

and `or`

by
building `if`

expressions with which you feel more
comfortable. But this can be dangerous, because you have to handle
all of the alternative cases properly. The structure of an
`if`

or a `cond`

is considerably more
complex. The connectives may be sugar, but their sweetness is
worth a little effort.

What if `n`

is 0 in the above expressions? What should
`and`

and `or`

return then? We can take a
hint from the patterns established here. `and`

looks
for "false" conjuncts. If it finds one, then it returns false;
otherwise, it continues to look. When there are no conjuncts,
it will not find a false one, *so and returns
true*. (Think of this from the perspective of a recursive
function...)

Likewise, `or`

looks for "true" disjuncts. If it finds
one, then it returns true; otherwise, it continues to look. When
there are no disjuncts, it will not find a true one, so
`or`

returns false.

#### Conditional Expressions as Syntactic Abstractions

We have had a glimpse of Racket's multi-part branching construct,
`cond`

, in a
previous lecture.
Here is an example of the use of a `cond`

:

> (define sign (lambda (x) (cond ((< x 0) -1) ((> x 0) 1) (else 0)) )) > (sign -2) -1 > (sign 4) 1 > (sign 0) 0

We can provide an inductive semantic translation of
`cond`

to `if`

as follows:

(cond (<p1> <e1>) (<p2> <e2>) . . . (<pn> <en>))

is equivalent to an `if`

expression:

(if <p1> <e1> (cond (<p2> <e2>) . . . (<pn> <en>)))

This definition is not complete, because it doesn't take into
account the optional `else`

expression. Still, this
gives us a good idea of what is going on.

Quick Exercise: How would you modify the definition to take`else`

into account?

The biggest difference between `if`

and
`cond`

is that `cond`

allows more than two
choices. Any expression that can be written using one can be
rewritten using the other.

Quick Exercise, in Reverse: Give an inductive translation of an`if`

expressions into a`cond`

expression.

The fact that all `if`

s can be written as equivalent
`cond`

s means that neither expression is more
fundamental than the other. An interpreter that can handle one
can also handle the other, as well as logical connectives, as
syntactic abstractions.

#### Case Analysis as Syntactic Abstraction

Racket provides a `case`

special form that is similar to
the `switch`

statements of C/C++ and Java. (Check it
out in
the Racket Guide.)
.
If you would like to practice more with the idea of conditional
operators as syntactic abstractions, use the translations given
in this reading to determine the main differences between a
`case`

expression and a more general conditional
construct. You might even try writing an inductive definition
for it!