views:

794

answers:

8

Is this just a bit of historical cruft left over from the 1950s or is there some reason syntactically why multi-expression bodies of (if) forms require (progn)? Why can't you wrap the multiple expressions in a set of parentheses like with (let):

   (if some-cond
     ((exp1) (exp2) (exp3)) ; multi exp "then"
     (exp4)) ; single exp "else"

It appears it would be trivial to write a macro to test each body to see first if it is a list and then if it is, if its first element is also a list (and thus not a function invocation) and then to wrap its subcomponents inside a (progn) accordingly.

+1  A: 

There are already macros for the expanded version. This book is really good: http://www.gigamonkeys.com/book/ Your answer is in this chapter: http://www.gigamonkeys.com/book/macros-standard-control-constructs.html

The standard macros are when and unless.

Phil
But (when) and (unless) don't have else branches.
+3  A: 

Not all expressions are lists. For (let ((a 42)) (if some-cond (a b c) (d e f))) you would not know whether (a b c) should be interpreted as a call to the function a or as an implicit progn.

No, according to my description since (a b c) is a list and its first element is not also a list, it is a function invocation and doesn't require the progn.
+1  A: 

In Lisp, parentheses indicate function application, not grouping. What would your expression mean if exp1 was a function that returned a function? Would it be called with the arguments (exp2) (exp3) or not?

Greg Hewgill
If the first sub expressions was also a list then all of them would be wrapped in (progn)
+1  A: 

When you have no 'else' branch, standard macros when and unless help. Otherwise it's better to use cond if you have multiple expressions in any branch.

fionbio
+4  A: 

is there some reason syntactically why multi-expression bodies of (if) forms require (progn)?

The answer is "yes", although perhaps not for the reason you're expecting. Because Common Lisp (unlike Scheme and other Lisps) requires funcall, your proposal is not ambiguous. Even if it were ambiguous, as long as your users know that parentheses imply progn here, it would work.

However, no other constructions* in the language have optional single/double parentheses. Plenty of constructions have implicit progns, but their parenthetical syntax is always the same.

For example, cond has an implicit progn for each branch:

(cond (test1 body1) (test2 body2) ...)

You can't switch back and forth:

(cond test1 exp1 (test2 body2) t exp3)

So, even though your proposal isn't ambiguous, it doesn't fit the syntax of the rest of the language. HOWEVER! Like you said, the macro is trivial to implement. You should do it yourself and see if it works well. I could easily be wrong; I'm pretty biased since almost all my Lisping is in Scheme.

*Except case. Hmf. Now I think there may be others.

Nathan Sanders
+3  A: 

Common Lisp is not perfect because it is perfect, it is perfect because it is perfectable.

The whole language is built upon 25 special operators; if is one of those, progn is another.

if provides just the basic mechanism of testing a condition, then jumping to either one or the other code address. progn provides the basic mechanism of doing several things and returning the value of the last.

There are several macros in the language standard that build on this -- e.g. when, unless, cond, case.

If you want, you have several options to make something like what you envision: for one, you could write an ifm macro that expects implicit progns as then- and else-clauses, or you could write it like you said, so that it detects the intent, or you could even write a read macro to add syntactic sugar for progn.

Svante
+7  A: 

In Common Lisp, this code:

(if t
    ((lambda (x) (+ x 5)) 10)
   20)

will return 15. With your proposal, I think it would see that the true-clause is a list, and automatically convert it to:

(if t
    (progn (lambda (x) (+ x 5)) 10)
   20)

which would return 10. Is that right?

I'm not sure it's "trivial" to distinguish between "list" and "function invocation" in CL. Do you intend for this change to be non-backwards-compatible? (New and interesting Lisp dialects are always cool, but then it's not Common Lisp.) Or can you give an example of what you have in mind?

Ken
+1  A: 

Because the syntax for IF (<- HyperSpec link) is defined as:

if test-form then-form [else-form] => result*

There are no begin or end markers. There is a THEN-FORM and not THEN-FORM*. PROGN is a mechanism to define a sequence of forms, where the forms are executed from left to right and the values of the last form are returned.

It could have been defined like this:

my-if test-form (then-form*) [(else-form*)] => result*

(defmacro my-if (test then &optional else)
  (assert (and (listp then) (listp else)) (then else))
  `(if ,test (progn ,@then) (progn ,@else)))

(my-if (> (random 10) 5)
       ((print "high")
        :high)
       ((print "low")
        :low))

Well, there is already a construct that supports multiple forms: COND.

(cond ((> (random 10) 5)
       (print "high")
       :high)
      (t
       (print "low")
       :low))

The typical style is to use COND when multiple alternatives have to be tried and when there are multiple then/else-forms. IF is used when there is only one test and both a then and an else form. For other cases there is WHEN and UNLESS. WHEN and UNLESS support only one or THEN forms (no else form(s)).

I guess it is good to have at least one conditional form (IF in this case) that comes without added layers of parentheses. Writing

(if (> (random 10) 5)
    (progn
       (print "high")
       :high)
    (progn
       (print "low")
       :low))

is then a small price to pay. Either write the additional PROGNs or switch to the COND variant. If your code would really benefit from IF with multiple then and else forms, then just write that macro (see above). Lisp has it, so that you can be your own language designer. It is important though to think about introducing a macro: is my macro correct? does it check errors? is it worth it? is it readable (for others?)?

Rainer Joswig