### 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:

```(and test_1 test_2 ... test_n)
```

is equivalent to an `if` expression:

```(if test_1
(and test_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:

```(or test_1 test_2 ... test_n)
```

is equivalent to an `if` expression:

```(if test_1
true
(or test_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!