merits of Lisp vs Python

J

Jon Harrop

Ken said:
Steve, you seem to be doing everything you can to make what is basically
a decent cultural exchange unpleasant...

He has a point. Lispers always say that you can't see the superfluous
parentheses after a month of staring at them, yet you must match them. I do
prefer autoindenting though. Giving whitespace meaning seems like a
second-rate alternative to me...
 
P

Paul Rubin

Wolfram Fenske said:
Alex Mizrahi already took care of that one.

I'm not persuaded, I haven't examined his example carefully yet but it
looks like basically a reader hack. Lexical scope in Lisp means among
other things lexical closures and (maybe I'm mistaken) it seemed to me
Alex's example didn't supply that. I'm also unconvinced (so far) of
his description of call/cc as a Lisp macro but that's going to take me
some head scratching.
Scheme is also a Lisp. So?

No I don't buy that, you can't say Scheme is Lisp when it suits you to
do so and that it isn't Lisp at other times. I'd say you can map most
of Python's semantics onto Scheme reasonably straightforwardly, but
mapping them to CL is considerably harder, macros or no macros.
Anything in particular? I'd be surprised if the solutions in Scheme
and CL would differ very much

I took it back, I think you can do it in CL with closures (call/cc not
needed) similar to how it's done in SICP. You could do something
similar in Python but the natural way is with generators.
 
W

Wolfram Fenske

Steven D'Aprano said:
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.
 
B

Bill Atkins

Paul Rubin said:
No I don't buy that, you can't say Scheme is Lisp when it suits you to
do so and that it isn't Lisp at other times. I'd say you can map most

Quite right.
 
P

Paul Rubin

Jon Harrop said:
That is a deficiency of Python that doesn't exist in modern FPLs like OCaml,
SML, Haskell, F#...

Nothing stops you from re-using the same internal function name in
your Python code, like you might use "i" as a throwaway loop index in
several places in the same function. It's just like in Scheme, the
function is a first class object like an integer.
 
P

Paul Rubin

Wolfram Fenske said:
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() # ...

Do you know about Python's "with" statement? You'd define a class for
those db connections, that acquire the lock on entry, and release it
on exit.
 
J

Jon Harrop

Wolfram said:
But seriously... All the interesting features that haven't originated from
Lisp (e. g. OO from Smalltalk) could in turn easily be implemented in Lisp
with a couple of macros.

I was under the impression that CLOS was non-trivial to write. There are
many other interesting features that haven't originated from Lisp (e.g.
pattern matching in SML, OCaml, Haskell, F#, Mathematica, ...) that cannot
be implemented easily in Lisp because they are intrinsically complicated.
 
B

Bill Atkins

Steven D'Aprano said:
Your Python syntax is rather wonky, but that's incidental.

What is wonky about it? The use of +foo+ symbols from the Lisp? Is
there some particular bit of wonkiness you can point out that will
make me think you've done anything but intentionally misunderstand my
post?
Nine lines, including handling the case where control_code is none of the
four constants. Ten if you add the "pass" statement that it actually
needs. And it is completely self-contained, with no external functions or
macros to understand.

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. Fine, you claim them as boilerplate. I'm going to claim all
those unnecessary brackets as boilerplate.

We're not counting lines here, you goon. We're talking about how
expressive constructs are and how closely they match your concept of
what you want to do. The conditional example is lower-level; you're
talking to the interpreter instead of saying what you want to achieve.
You're having to repeat things because that's what the language asks
of you, instead of describing in a higher-level way what you're
actually doing.
Yes, I know the parser needs them. But as so many people keep telling me,
once you've been writing Lisp code for a month, you don't even notice the
brackets. That makes them unnecessary for the developer, and therefore
something the computer should handle on its own. You've already split
expressions with whitespace, why should you have to use brackets as well?
That's just boilerplate.

This simply means that the parentheses do not stand out. It doesn't
mean that they *literally* cease to become visible (unless I have
perhaps missed out on Lisp enlightenment).

Obviously, this doesn't mean that they are "unnecessary." They are
necessary to give Lisp its regular structure, which 1) makes macros
possible, 2) allows easy movement through and automatic indentation of
code and 3) is aesthetically pleasant, which leads to easier
maintenance. Brackets are no more boilerplate than the significant
whitespace in Python is boilerplate (that is, they're not boilerplate
at all).
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 you haven't understood it correctly, of course. The only reason
this works is that the macro is passed the unevaluated code, which
still has the original symbols that the programmer used. It can
insert these into the generated code as it pleases. I feel that
should be clear to someone who claims to know as much Lisp as you do.
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?

Is it really useful? Yes, it is.
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.

Did you read the DEFINE-BINARY-CLASS macro I mentioned elsewhere?
Where did you get the notion that ECASE was the best example of where
macros can be used? Why am I responding to another of your absurd
posts?
 
G

George Sakkis

Ken said:
To me in part it means "no one can decide to take away lambda because
they decided they should not have put it in in the first place and then
change their mind and leave it in after I already put a few people on
de-lambdifying our 100kloc system because..."

Funny, coming from the guy with an inclination in discovering straw man
arguments in others. Python 3.0 should be at least a year away from
now, let alone the time of discussions about lambda. If you're changing
a working system based on brainstorms and speculations about a
backwards incompatible version to appear after two years or three, I
can't feel sorry for you :)
, and in part it means
compilation. And it /has/ to have macros. :)

Oh, and I had the vague impression that it was somehow related to
standards about bleeding-edge technologies, such as sockets. But then,
I could be wrong :)
Metaprogramming by definition cannot be obscure. Metaprogramming means
programming at a higher level.
Check.

A higher level means "more productive".

*Segfault* *core dump* *kernel panic* :)

No, it is more productive only if you (and the other 5, 10, 100 people
in your group) can understand, implement, debug, test and maintain code
in that higher level of abstraction.

The rest of the proof is therefore irrelevant ;-)
You insult them by suggesting they cannot concieve at a higher level.
Mebbe they could if their language aupported it. Yours does not.

Actually it does. Metaclasses, descriptors, decorators are some high
level Python concepts. Most people are productive in Python for years
without ever having to write a custom metaclass, or even know their
existence (although a library or framework they use daily might in fact
use all of the above). Ever heard of Zope ? Even though it's written in
Python, it is criticised for its steep learning curve, one main reason
being its high level of abstraction.
Of course we would. You do not get it. The only thing you guys are
bleating about is greater popularity. Now someone is about to eat your
lunch popularity-wise. Live by the poll, die by the poll. And you held
first place for like a week in language geologic time, taken down merely
because...


... a little hype came along? Wow, your fundamental advantage must have
been about zip, and all derived from things unrelated to syntax. <gasp>

Well, for one thing we never held the first place anyway. Second, what
appears to be the big boost for Ruby today maybe its death sentence
tomorrow, ending up as a niche language for web apps, like PHP today.
With a large crowd undoubtly, but I bet most Pythonistas would prefer
lower popularity than ending up in a corner of the application
spectrum.
, but Ruby is much closer to Python than

As Graham said, if some pretty good programmers are jumping up and down
about X, mebbe you should take a look at X. Even if assholes like me
piss you off. :)

Change that to "amuse me"; nothing better than a sense of
self-knowledge :) If you mean the same Graham that wrote
http://paulgraham.com/pypar.html, perhaps we don't disagree that much
after all :)

George
 
J

Jon Harrop

Wolfram said:
Sure, you can shoot yourself in the foot with macros. But you can do
that in any language of any degree of expressiveness [3]. Come to
think of it, the whole reason why we use high level languages is
because of their expressiveness: We get stuff done faster and
introduce less errors. So "the more expressive, the better," right?
But according to you, there's a point when a language gets "too
expressive". I don't see why.

Is Qi Lisp? There are several problems:

1. Language design is so hard that most people get it wrong (e.g. Python).
What do you do when you find that your proprietary syntax extension was a
bad idea?

2. There is a trade-off between being expressive and sacrificing syntax.
Lisp is very verbose because you must write the AST for your code by hand,
even if you aren't exploiting s-exprs, EVAL and macros.

3. One could argue that every syntax extension in Lisp makes a new language.
So Lisp is a heavily forked language. Is that a good?

Maybe if you're a lone coder and an expert in language design then you can
knock up a DSL and halve your working time using Lisp. In industry, you're
more likely to hire four people with half the expertise each and restrict
them to a well-known C-like syntax.
Sure, it's a powerful tool but it's not *that* hard to use.

Look at all the threads in c.l.l where people have gotten confused by their
own macros. Look at the unboxing macro that Juho Snellman used to try to
get competitive performance for Lisp on my ray tracer benchmark:

(defmacro def ((name params &body body)
(mname &rest mparams)
(wname &rest wparams))
`(progn
(declaim (inline ,name ,wname))
(defun ,name ,params
(declare (type double-float ,@params))
,@body)
(defmacro ,mname ,(mapcar #'car mparams)
,(loop with inner = (list name)
with body = ``,',inner
with all-names = nil
for (form count) in (reverse mparams)
for names = (loop repeat count collect (gensym))
do
(setf all-names (append all-names names))
(setf body ``(multiple-value-bind ,',(reverse names)
,,form ,,body))
finally
(setf (cdr inner) (reverse all-names))
(return body)))
(defun ,wname ,(mapcar #'car wparams)
(,mname ,@(mapcar #'cadr wparams)))))

Note that this page of magic does something done by the compiler in most
other languages.
Maybe you're afraid of it because that it's something that's unique to
Lisp?

It isn't unique to Lisp. OCaml has camlp4, for example. It is very rarely
used, perhaps because the OCaml community is wary of syntax extensions or
perhaps because it is even harder to use than Lisp's macros.
But IMO the reason for that is not that they're too powerful. IMO it
has mostly to do with the fact that other languages' syntaxes make it
too difficult to implement Lisp-style macros.

I agree. However, the fact that other languages ship with sophisticated
syntax can also be a good thing: it can make them more concise, it can make
them more expressive (e.g. pattern matching).

I get the impression that Lispers use macros when they could use HOFs.
Come on! You're telling me people don't learn Lisp because they are
afraid of it? Python allows you to redefine built-in functions, as
you said, Ruby allows you to attach new methods to live objects, but
Lisp is simply going too far?

The biggest activation barrier to using Lisp for me is having to write ASTs
for everything by hand. Perhaps it would be a good idea to include an
editor that allowed newbies to use a more familiar syntax and have it
converted to s-exprs for them, so they can see the correspondence and learn
how to write s-exprs themselves?
Easy because macros are not functions. Functions allow you abstract
functionality, macros allow you abstract syntax.

While that is true, you've provided two examples to back up his point. In
another post you use the example of wrapping try .. finally which can be
done with a HOF instead of a macro. Next, you use the example of COND,
which can also be written as a HOF instead of as a macro:
Look at the examples
above. How would you implement conditional expressions as a function?
Answer: You can't, it's syntax.

That is not true. You can implement conditional expressions without
extending the syntax:

# let rec cond x rules default = match rules with
| [] -> default
| (f, e) :: _ when f(x) -> e
| _ :: t -> cond x t default;;
val cond : 'a -> (('a -> bool) * 'b) list -> 'b -> 'b = <fun>

For example:

# cond 2
[( = ) 1, "one";
( = ) 2, "two";
( = ) 3, "three"]
"neither one, two nor three";;
- : string = "two"

Of course, this is pointless in OCaml because the built-in pattern matcher
is more expressive, powerful and faster than COND:

# match 2 with
| 1 -> "one"
| 2 -> "two"
| 3 -> "three"
| _ -> "neither one, two nor three";;
- : string = "two"
 
W

Wolfram Fenske

Paul Rubin said:
[...]
Scheme is also a Lisp. So?

No I don't buy that, you can't say Scheme is Lisp when it suits you to
do so and that it isn't Lisp at other times.

OK, I cheated a bit. (Although when I say "Lisp" I don't necessarily
mean Common Lisp. Scheme is also Lisp.) You are right about call/cc
and probably also about lexical variables. I have to modify my
statement from "All the interesting features that haven't originated
from Lisp could easily be implemented in Lisp" to "A lot of the
interesting features ...". Assertions starting with "all" are a
dangerous business because usually someone comes along with a
counter-example. I should have been more careful when I wrote that.

What I meant to say with the two sentences "a lot of 'modern' language
features have originated from Lisp" and "a lot of the interesting
features that haven't originated from Lisp could easily be implemented
in Lisp" was that I believe that Lisp has hit a sweet spot in the
programming language continuum. It must have been right about a lot
of things to still be alive and kicking after all this time.

[...]
I took it back,

Sorry, I only noticed that after posting.
 
J

Jon Harrop

Paul said:
Nothing stops you from re-using the same internal function name in
your Python code, like you might use "i" as a throwaway loop index in
several places in the same function. It's just like in Scheme...

It is not "just like in Scheme" if you don't have anonymous functions in
Python.
 
K

Kay Schluehr

A few months ago, I missed the Condition System most
when using Python, and also lexical scope. However, it is nice to work
with friends, who know Python and not Lisp.)

Could you explain in which way Python lacks lexical scoping?
 
W

Wolfram Fenske

Paul Rubin said:
Do you know about Python's "with" statement? You'd define a class for
those db connections, that acquire the lock on entry, and release it
on exit.

Yes, I wrote about it in another post. It was introduced in Python
2.5. And if it hadn't been I'd still have to write code like this.
 
J

Jon Harrop

Paul said:
Haskell is pretty neat in that it basically removes things like setq.
That makes possible (for example) very cool simplifications to
concurrent programming. See the paper "Composable memory transactions":

http://research.microsoft.com/~simonpj/papers/stm/index.htm

Yeah you could do that with Lisp by writing in a restricted style,
just like you could avoid pointer errors in C++ by writing in a
restricted style. But it's hard for the compiler to guarantee that
your program obeys the restrictions. You could write a language like
Ada or Java that makes a lot of guarantees, but is unexpressive and a
huge pain to program in. You can write a language like Lisp that's
expressive but doesn't make as many guarantees. Interesting problems
in language design arise in writing languages that are expressive AND
make guarantees.

I discovered this recently with F#. Although F# (as a dialect of OCaml) is
impure like Lisp, it does make purely functional programming easy and
provides many purely functional data structures. I translated 15kLOC of
mostly-functional OCaml code into F# and only had to add four locks to make
the whole library concurrent.

That is probably feasible in Lisp but not in Python...
 
P

Paul Rubin

Jon Harrop said:
# cond 2
[( = ) 1, "one";
( = ) 2, "two";
( = ) 3, "three"]
"neither one, two nor three";;
- : string = "two"

I'm missing something. Doesn't Ocaml have strict evaluation? That
means if you use function calls instead of string constants in those
values, they all get called. You haven't really done what cond does.
 
K

Kay Schluehr

George said:
Well, for one thing we never held the first place anyway. Second, what
appears to be the big boost for Ruby today maybe its death sentence
tomorrow, ending up as a niche language for web apps, like PHP today.
With a large crowd undoubtly, but I bet most Pythonistas would prefer
lower popularity than ending up in a corner of the application
spectrum.

Who really wants to write web apps? Web apps are just an excuse for
Pythonistas to write web frameworks.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,434
Messages
2,571,690
Members
48,796
Latest member
Greg L.

Latest Threads

Top