Edit: thanks folks! You've helped me get over the facepalm from years ago :)
Why if seems to be commonly defined as (condition then-clause &rest else-clauses) instead of (condition then-clause else-clause), I don't know. History/common use patterns, probably?
It's not really separating related semantics; it's that an if-then-else option has to have both a then clause and and else clause. (IF then else) is a natural way to express that, but it does mean that if you want to do more than one thing in the then clause then you'll need to have a PROGN. You could have multiple statements in the else clause (which is what emacs does).
In Common Lisp, the syntax is:
if test-form then-form [else-form] => result*
In emacs, it's: if test-form then-form [else-form]* => result*
I think that the emacs form is weird and annoying, but might make certain forms of code easier (e.g. check for something and short-circuit, else calculate something more deeply).COND is a different beast entirely.
Note that WHEN & UNLESS both have an implicit PROGN.
I.e, I would've expected something like this:
(if (condition)
(
(then do something)
(and more things)
)
(
(else do something else)
(and more other things)
)
)
(and yeah, I'm definitely showing my C-semantic preferences here aren't I?) (if (eq foo bar) (baz))
looks like a function call, not returning the value of baz (using your syntax).And of course Lisp is often written in a semi-functional way, so PROGN is, while not rare or really even avoided, not the usual way of doing things, usually.
(cond
(<condition1> <body>*)
(<condition2> <body>*)
(else <body>))See, this is what happens when you know Scheme better than CL.
That is, unless we introduce a signal, like an else keyword/label which separates them, like this:
(if cond form form form
else form form form)
Such macros have existed. A certain John Foderaro of Franz, Inc. was (is?) known for favoring and promoting his if{star} macro. (Of course I mean asterisk by {star} which HN won't let me type).Info here:
http://franz.com/~jkf/coding_standards.html
http://franz.com/support/documentation/6.0/doc/operators/exc...
Your idea is certainly doable:
(my-if cond
(then-form then-form ...)
(else-form else-form ...))
The macro is trivial: (defmacro my-if (cond then-forms &optional else-forms)
`(if ,cond
(progn ,@then-forms)
(progn ,@else-forms)))
I've always resisted writing this macro because I felt it has a slight readability issue: (my-if (some condition)
((consequent)
(consequent2)
(consequent3))
((alternative) ;; relatively quiet signal here
(alternative)))
Maybe it's not that bad, after all. Still, it's just saving a few keystrokes to insert progn and doesn't buy much over cond, which is more general, allowing multiple conditions: (cond
((some condition)
(consequent)
(consequent2)
(consequent3))
((other condition)
(other)
(other2))
(t (alternative)
(alternative)))
NOTE: cond existed in Lisp first, as a special form. The if macro came later as a syntactic sugar for simple situations involving just one condition!The thing is that if you keep most of your code functional, a lot of this is moot. We need multiple statements when we are doing something imperative. There is never any reason to have (progn S1 S2) unless S1 has a side effect. If S1 has no side effect, then this is equivalent to (progn S2), which is just S2. If you avoid side effects, the you don't need any progn most of the time (implicit or not).
This is why progn is called what it is; it's Lisp's feature for writing a "program" (in the sense of an imperative list of things to do). Well, "prog" is that feature; and "progn" is the variant which returns the value of the n-th form.
Another mitigation is that lot of the time code binds variables anyway with a let:
(if condition
(let (... vars ...)
this
that)
(let (.... other vars ...)
other))(if a (something because of a) (something more because of a))
Requiring a progn for the else clause means that the above is caught by the compiler.
Secondly, because lisp, while not a functional language, has functional leanings: you would be surprised how often it is that one form is all you need.
CL-USER 48 > (LOOP REPEAT 2
FOR number = (READ)
IF (> number 0) DO
(princ '(the number is larger than 0))
(print 'true)
(terpri)
ELSE DO
(princ '(the number is not larger than 0))
(print 'false)
(terpri))
2
(THE NUMBER IS LARGER THAN 0)
TRUE
-2
(THE NUMBER IS NOT LARGER THAN 0)
FALSE
NIL
It is also not that difficult to write a multi-expression IF. Here is a sketch: (defmacro if* (cond &rest conseq)
(let ((p cond)
(thenpos (position 'then conseq))
(elsepos (position 'else conseq)))
(cond ((and thenpos elsepos)
`(if ,p
(progn ,@(subseq conseq (1+ thenpos) elsepos))
(progn ,@(subseq conseq (1+ elsepos)))))
(thenpos `(when ,p ,@(subseq conseq (1+ thenpos))))
(elsepos `(unless ,p ,@(subseq conseq (1+ elsepos)))))))
Then we can write: (if* (> pi 0)
then (print 'a) (print 'b)
else (print 'c) (print 'd))
and (if* (> pi 0)
then (print 'a) (print 'b))
and (if* (> pi 0)
else (print 'a) (print 'b))Well, it is, but you can't do it in the general case.
You can define a macro for it, though.
(In C, you can say "x = x + 1" or "x += 1" or "x++", where the first one is enough. Still I like having the other two.)