merits of Lisp vs Python

Wolfram Fenske int2k at gmx.net
Sun Dec 10 03:03:11 EST 2006


Steven D'Aprano <steve at REMOVE.THIS.cybersource.com.au> writes:

> On Sat, 09 Dec 2006 14:57:08 -0500, Bill Atkins wrote:
>
>> Paul Rubin <http://phr.cx@NOSPAM.invalid> writes:
>>
>>> There is just not that much boilerplate in Python code, so there's
>>> not so much need to hide it.
>>
>> Well, of course there is.  There are always going to be patterns in
>> the code you write that could be collapsed.  Language has nothing to
>> do with it; Lisp without macros would still be susceptible to
>> boilerplate.
>>
>> Here's a concrete example:
>>
>>   (let ((control-code (read-next-control-code connection)))
>>     (ecase control-code
>>       (+break+
>>         (kill-connection connection)
>>         (throw :broken-by-client))
>>       (+status+
>>         (send-status-summary connection))
>>       ((+noop+ +keep-alive+))))
>>   ;; +X+ indicates a constant
>
> Eight lines, excluding the comment, and it doesn't handle the case where
> control-code is not one of +break+, +status+, +noop+ or +keep-alive+,
> although the ecase macro does. And how many lines of code is ecase?

Doesn't matter because it only has to be written once.  For Common
Lisp it's already part of the language.  It would of course be bad to
write a macro if pattern comes up only a few times.  But that's not
how macros are used.

>> All of that boilerplate is handled by the macro.  In Python, I
>> would need to do something like:
>>
>>   control_code = connection.read_next_control_code()
>>   if control_code == +break+:
>>     connection.kill()
>>     throw blah
>>   else if control_code == +status+:
>>     connection.send_status_summary()
>>   else if control_code == +noop+ || control_code == +keep_alive+:
>>   else:
>>     error "CONTROL_CODE fell through conditional cascade; was not one of +BREAK+, +STATUS+, +NOOP+, +KEEP_ALIVE+"

[...]

> Saving one line of code, at the expense of having another block of code to
> write or understand -- is that really the best example of what macros are
> used for in practice? You don't really save writing any boilerplate code,
> except for else clause, unless you're claiming that "if" and "elif" is
> boilerplate.

No, all the "if control_code == SOME_STATUS:" are boilerplate:

--8<---------------cut here---------------start------------->8---
   if control_code == STATUS1:
     action_1
   else if control_code == STATUS2:
     action_2
   ...
   else if control_code == STATUSN:
     action_n
   else:
     Exception(...)
--8<---------------cut here---------------end--------------->8---

The pattern is that one value is compared against several possible
values and depending on which one matches, the appropriate action is
taken.  Common Lisp's CASE and ECASE macros and the "switch"
statements of C or Java make this pattern explicit and arguably easier
to recognize.  In Python you have to type it out yourself.

[...]

> Okay, I'm impressed that ecase can pick up on the four constants being
> tested against, and feed their names (rather than merely their values)
> into an error message. Python has nothing like that, and if you only have
> three or four things to test against, *and* they have names, that could be
> a useful thing to do. And if I've understood correctly, that's more a
> feature of Lisp's symbols than of macros.
>
> But if you're testing against fifty different values, well, is it really
> useful for your error message to list all fifty names? Or do you have
> another macro ecase-with-lots-of-tests?

Now you're being silly.

[...]

> If that's the best example of what macros can be used for, frankly I'm
> unimpressed. Yes, I can see some benefit. But I don't see that the benefit
> is worth the added complexity. Maybe there are more complex tasks that
> macros are better suited for.



Here's another example.  I had to use a database in one of my Python
programs.  Everytime I used the database I had write something like
this:

--8<---------------cut here---------------start------------->8---
self.lock.acquire()
try:
    connection = sqlite.connect(self.dbFile)
    connection.row_factory = dictFactory
    try:
        # do something with the connection
    finally:
        connection.close()
finally:
    self.lock.release()
--8<---------------cut here---------------end--------------->8---

In Lisp, I could just write a macro "WITH-CONNECTION" that takes the
name of the connection variable and the code for "do something with
the connection" as arguments.  Actually, I ended up writing something
like it as a function in Python:

 --8<---------------cut here---------------start------------->8---
        def withConnection(self, fun):
            self.lock.acquire()
            try:
                connection = sqlite.connect(self.dbFile)
                connection.row_factory = dictFactory
                try:
                    return fun(connection)
                finally:
                    connection.close()
            finally:
                self.lock.release()
--8<---------------cut here---------------end--------------->8---

What would have been the body in the Lisp macro has to be supplied as
function.  It's basically a macro, but it still requires more typing
because you have to define a local function eveytime it is used.
Also, there are things you can do with Lisp macros where this
mechanism isn't applicable.

--
Wolfram Fenske

A: Yes.
>Q: Are you sure?
>>A: Because it reverses the logical flow of conversation.
>>>Q: Why is top posting frowned upon?




More information about the Python-list mailing list