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)))))
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
Code: Select all
(eval-if '(if)) ;=> nil
(eval-if '(if 42)) ;=> 42
(eval-if '(if (+ 2 40))) ;=> 42
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
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
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"
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. :)