I believe that this is a great choice, because newLISP's if is a general type of if. What do I mean by this? In the manual discussion, we are presented with the two different forms of if; but in fact, the second form (the one that takes an arbitrary number of clauses) is just a generalization of the first form.[1]

Lutz may not like for me to say this, but there is a beauty and a consistency[2] in its simple semantics. (Programming language geeks like me love it, but I think other users would too. This is something that I think can bring us all together. :)

I believe that Lutz made a great decision in choosing the semantics of newLISP's if expression, and in the following, I am going to detail why this is so.

The arguments to if are a list of clauses, which in the non-degenerate cases, are pairs of test and consequent clauses. The situation looks like this:

( if test-clause-1 consequent-clause-1 . . . test-clause-N consequent-clause-N )

These pairs are continually looped though until the first such test clause evaluates to a "truthy" value, in which case its corresponding consequent clause is evaluated and yielded as the value of the if expression itself.

The degenerate cases are when the if expression has one clause (argument) or none. But these cases can be thought of as base cases of a recursive evaluator for if. In fact, you can write such an evaluator in newLISP. Here is one.

- Code: Select all
`(define (eval-if EXPR)`

(if (empty? EXPR) nil

(= 'if (EXPR 0)) (eval-if (1 EXPR))

(= 1 (length EXPR)) (eval (EXPR 0))

(let (test-clause (EXPR 0)

consequent-clause (EXPR 1)

rest-of-the-clauses (2 EXPR))

(if (eval test-clause)

(eval consequent-clause)

(eval-if rest-of-the-clauses)))))

There is something important lurking here, and it is that this function describes the semantics of newLISP's if in newLISP itself!!! Yeah, that's cool.

Now, it is clear why the degenerate cases yield the values they do for newLISP's if. Here are some examples of these cases.

- Code: Select all
`(if) ;=> nil`

(if 42) ;=> 42

(if (+ 2 40)) ;=> 42

Our evaluator treats them the same.

- Code: Select all
`(eval-if '(if)) ;=> nil`

(eval-if '(if 42)) ;=> 42

(eval-if '(if (+ 2 40))) ;=> 42

Next, examples from the first form of if in the manual look like this.

- Code: Select all
`(set 'x 50)`

(if (< x 100) "small" "big") ;=> "small"

(set 'x 1000)

(if (< x 100) "small" "big") ;=> "big"

(if (> x 2000) "big") ;=> nil

Our evaluator in action now.

- Code: Select all
`(set 'x 50)`

(eval-if '(if (< x 100) "small" "big")) ;=> "small"

(set 'x 1000)

(eval-if '(if (< x 100) "small" "big")) ;=> "big"

(eval-if '(if (> x 2000) "big")) ;=> nil

Finally, the examples from the second form of if in the manual.

- Code: Select all
`(define (classify x)`

(if (< x 0) "negative"

(< x 10) "small"

(< x 20) "medium"

(>= x 30) "big"

"n/a"))

(classify 15) ;=> "medium"

(classify 100) ;=> "big"

(classify 22) ;=> "n/a"

(classify -10) ;=> "negative"

Our evaluator.

- Code: Select all
`(define (classify-eval-if x)`

(eval-if '(if (< x 0) "negative"

(< x 10) "small"

(< x 20) "medium"

(>= x 30) "big"

"n/a")))

(classify-eval-if 15) ;=> "medium"

(classify-eval-if 100) ;=> "big"

(classify-eval-if 22) ;=> "n/a"

(classify-eval-if -10) ;=> "negative"

__________

[1] No doubt though that the manual presents two forms of if to the user because the first form is well-known; so it will communicate its ideas (about it and the second form) best to the broadest section of readers.

[2] There you go, Lutz; I used both terms. :)