merits of Lisp vs Python

P

Paul Rubin

tmh said:
I've been writing code for engineering solutions for 15 years in
various languages. I've gained more insight into coding in the last 6
months then in the previous 15 years. Since lisp allows you to employ
any and every programming technique, it actually requires you to
understand the techniques well enough to apply them when appropriate.

You might try Mozart said:
You should study lisp for at least a year, use it for some decent size
projects. At the end of the day, even if you decide not to continue
using it, you will be a much better coder. My bet, though, is that if
you use it for a year, you won't want to use anything else.

I've used Lisp for a long time and I've implemented it from scratch
(small dialects, not full CL) more than once. There's something
primordial about it that is very satisfying to the inner urges. But
there are higher forms of life out there these days too.

Do you know the Paul Graham piece "Beating the Averages"? It's at:

http://www.paulgraham.com/avg.html

The error in it is that Lisp is really just another Blub.

http://weblog.raganwald.com/2006/10/are-we-blub-programmers.html
 
A

Alex Mizrahi

(message (Hello 'Kay)
(you :wrote :eek:n '(8 Dec 2006 12:25:09 -0800))
(

KS> O.K. I agree with what you said about the generic function vs per
KS> object dictionary dispatch.
KS> But do the performance differences vanish when only builtin types and
KS> functions are used to express Python algorithms?

no.
language semantics require python to do dict lookup on each access to some
global function (or builtin) or variable.
i don't have enough time for in-depth analysis, but here's what's it.
suppose we have

def Fib(n):
if n < 2:
return 1
else:
return Fib(n -2) + Fib(n-1)

import dis
dis.dis(Fib)

you will see this:
....
21 LOAD_GLOBAL 1 (Fib)
24 LOAD_FAST 0 (n)
27 LOAD_CONST 2 (1)
30 BINARY_SUBTRACT
31 CALL_FUNCTION 1
34 LOAD_GLOBAL 1 (Fib)
37 LOAD_FAST 0 (n)
40 LOAD_CONST 1 (2)
43 BINARY_SUBTRACT
44 CALL_FUNCTION 1
47 BINARY_ADD
48 RETURN_VALUE

now let's check what is LOAD_GLOBAL in ceval.c (i have Python 2.4.1
sources):

case LOAD_GLOBAL:
w = GETITEM(names, oparg);
if (PyString_CheckExact(w)) {
/* Inline the PyDict_GetItem() calls.
WARNING: this is an extreme speed hack.
Do not try this at home. */
long hash = ((PyStringObject *)w)->ob_shash;
if (hash != -1) {
PyDictObject *d;
d = (PyDictObject *)(f->f_globals);
x = d->ma_lookup(d, w, hash)->me_value;
if (x != NULL) {
Py_INCREF(x);
PUSH(x);
continue;
}
d = (PyDictObject *)(f->f_builtins);
x = d->ma_lookup(d, w, hash)->me_value;
if (x != NULL) {
Py_INCREF(x);
PUSH(x);
continue;
}
goto load_global_error;
}
}
/* This is the un-inlined version of the code above */
x = PyDict_GetItem(f->f_globals, w);
if (x == NULL) {
x = PyDict_GetItem(f->f_builtins, w);
if (x == NULL) {
load_global_error:
format_exc_check_arg(
PyExc_NameError,
GLOBAL_NAME_ERROR_MSG, w);
break;
}
}
Py_INCREF(x);
PUSH(x);
continue;

so we can see PyDict access. moreover, it's inlined, since it's very
performance-critical function.
but even inlined PyDict access is not fast at all. ma_lookup is a long and
hairy function containing the loop.
moreover, we can see that there are two dict lookups -- into globals and
builins.
lookup into a global hash should about order of magnitude slower than simple
table fetch, so here's the root of python's slowness.

how lisp can be faster here? lisp has SYMBOLs and well-defined semantics of
source code parsing.
first source code is processed by reader, that outputs trees of code. each
variable or function name becomes a SYMBOL object. symbols are typically
interned into packages, but they don't need to be looked-up in the packages
in runtime -- in fact, it's not possible at all.
i can read a piece of code and then unintern some symbol from package --
that will not make that code invalid. packages are used mostly by reader.
(also, i can have many symbols with same name, if they are not interned --
and they will be all different objects)
in runtime, lisp has to lookup symbol's function -- but symbol can be
implemented as a structure, and symbol-function can be just it's field
access.

one more thing -- you can see lookup into builins, so they are in dict too!
and you can see that builtins dict is checked only if name is not found in
globals, so builtins are even slower than globals. that's a reason for
performance slowdowns too.
one can say that it's the only way to change builtins in runtime. yes, but
dict lookup is a big price for it (well, if python use symbols, it would be
faster).
in fact, Common Lisp also allows to redefine "built-in" function -- you can
define a symbol with a same name as builtin in some package and use it, it
will "shadow" symbol in the common-lisp package (common-lisp:+). you can
change symbol-function of this symbol in runtime and do whatever you want.
but symbols in common-lisp package are immutable. that makes it possible to
optimize code.

and there's inlining. for example, in fib definition:

(defun fib (n)
(if (< n 2)
1
(+ (fib (- n 2))
(fib (- n 1)))))

CLISP does not even use symbol FIB in function bytecodes -- it notes that
it's a recursive function calls, so instead of normal function call it does
a local jump.

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"People who lust for the Feel of keys on their fingertips (c) Inity")
 
K

Ken Tilton

Steven said:
It is a good thing that not every
hare-brained idea that some random programmer comes up with can be
implemented as part of the core language.

Well, that's the FUD/strawman, but nothing more. Just a hobgoblin to
keep the Pythonistas from straying. But you have an excuse: Lispniks
always /talk/ about macros giving us the ability to create a DSL. But no
one does. :) Macros mostly hide implementation -- always a good thing --
where functions cannot.

This, btw, is why Norvig brushing off macros with the ability to use
regex to parse a DSL was so disappointing.

I guess your other excuse is that your BDFL says the same thing.

All in all, this is getting pretty funny. I am starting to picture you
all (including GvR) running around with your hands over your ears going
woo-woo so you cannot hear what we are saying.

:)

ken

--
Algebra: http://www.tilton-technology.com/LispNycAlgebra1.htm

"Well, I've wrestled with reality for thirty-five
years, Doctor, and I'm happy to state I finally
won out over it." -- Elwood P. Dowd

"I'll say I'm losing my grip, and it feels terrific."
-- Smiling husband to scowling wife, New Yorker cartoon
 
S

Steven D'Aprano

David Lees wrote:
Those raving about
Lisp are quite accomplished at all those other languages, and know about
what they are talking.

Such a sweeping generalization. Every person who raves about Lisp is also
accomplished with other languages. Yeah, right. I believe you, even if
millions wouldn't.

I doubt the Pythonistas weighing in on this
thread ever got far at all with Lisp, so... should they really be
offering comparative analysis?

I hit my hand with a hammer once. I didn't keep going until I was an
expert in hitting-own-hand-with-hammer before deciding that hitting my
hand with a hammer was not for me. Did I do the wrong thing? Should I have
kept going until I was an expect at it?

(Of course, writing Lisp isn't precisely like hitting one's hand with a
hammer. With the hammer, the endorphins kick in eventually, and it can
become quite pleasant...)

Yeah, you are, you just did not use it heads down for a month.

The sheer arrogance of this claim is astounding.

Actually, this is comp.lang.lisp. It isn't astounding at all.

I don't know, maybe lisp coders actually are more intelligent than
ordinary mortals, but it has been my experience that they have absolutely
no grasp whatsoever of the way most (many? some?) people think. And I'm
not talking about can't-walk-and-think-at-the-same-time people either, I'm
talking about bright, intelligent people who, nevertheless, don't agree
with lisp coders.

The way
to tell if you spent enough time on Lisp is to look at Lisp code. If you
see any parentheses, you have not spent enough time. They disappear in a
month.

If the parentheses are that meaningless, why do you need them?

The typical Pythonista values clean code but trembles in the face of
macros, which exist to hide boilerplate.

Funny, when I write code, I try to remove boilerplate, not hide it.

That means the only thing
showing in any given block of code is exactly the interesting variable
and function names. Talk about readability.

Yes. And your point is?

No, languages are not interchangeable.

Perhaps you should consider what the term "Turing complete" implies.

Python is a fine language, but
Lisp is much more expressive/powerful.

Maybe so. A bulldozer is a lot more powerful than a tack-hammer, but if
somebody suggested using a bulldozer to lay carpet, I'd politely show them
to the door. Sometimes more power isn't better.
 
K

Ken Tilton

Paul said:
Do you know the Paul Graham piece "Beating the Averages"? It's at:

http://www.paulgraham.com/avg.html

The error in it is that Lisp is really just another Blub.

http://weblog.raganwald.com/2006/10/are-we-blub-programmers.html

There we find: "But when our hypothetical Blub programmer looks in the
other direction, up the power continuum, he doesn't realize he's looking
up. What he sees are merely weird languages... Blub is good enough for
him, because he thinks in Blub."

What is up the power continuum from Lisp?

ken

--
Algebra: http://www.tilton-technology.com/LispNycAlgebra1.htm

"Well, I've wrestled with reality for thirty-five
years, Doctor, and I'm happy to state I finally
won out over it." -- Elwood P. Dowd

"I'll say I'm losing my grip, and it feels terrific."
-- Smiling husband to scowling wife, New Yorker cartoon
 
A

Alex Mizrahi

(message (Hello 'Paul)
(you :wrote :eek:n '(08 Dec 2006 17:15:32 -0800))
(

PR> Huh? Are you saying Lisp systems never release new versions? And you
PR> can't implement Python generators as Lisp macros in any reasonable
PR> way. You could do them in Scheme using call-with-current-continuation
PR> but Lisp doesn't have that.

we can implement Scheme's call-with-current-continuation first :)
it's relatively easy -- just a code walker that coverts everyting into CPS.
i've used one for a web application development.

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"People who lust for the Feel of keys on their fingertips (c) Inity")
 
S

Steven D'Aprano

if Common Lisp didn't have CLOS, its object system, I could write my own
as a library and it would be just as powerful and just as easy to use as
the system Common Lisp already provides. Stuff like this is impossible
in other languages.

Dude. Turing Complete. Don't you Lisp developers know anything about
computer science?

Anything any language can do is possible in any other language, if you are
willing to write your own libraries. And debug them. Let's not forget the
debugging and testing, unless you'd like us to believe that Lisp code
never contains bugs. Lisp developers so often gloss over that: "Oh,
feature X is *easy*, I could write it in a couple of macros. Two or three.
Maybe thirty. Or forty, max. And they would work the right way first time.
No, I haven't actually done it myself. But I'm sure I could do it, easy."
 
R

Ramon Diaz-Uriarte

On 08 Dec 2006 19:56:42 -0800, Paul Rubin

(...)
Lisp just seems hopelessly old-fashioned to me these days. A
modernized version would be cool, but I think the more serious
Lisp-like language designers have moved on to newer ideas.

Paul, I find most of your comments well thought. But I don't follow
these. Could you elaborate?

a) "old-fashioned"? Is that supposed to be an argument? I guess
addition and multiplication are old-fashioned, and so is calculus;so?
I think "old-fashioned" should only carry a negative connotation in
the fashion world, not in programming.


b) "the more serious Lisp-like language designers have moved on to
newer ideas." Can you elaborate? I am not an expert but by looking at,
say, lambda the ultimate, I'd say this statement is just not true. And
which are these "newer ideas"; what programming languages are
incorporating them? (Scala, Mozart/Oz, Alice-ML, ...).


R.



--
Ramon Diaz-Uriarte
Statistical Computing Team
Structural Biology and Biocomputing Programme
Spanish National Cancer Centre (CNIO)
http://ligarto.org/rdiaz
 
A

Alex Mizrahi

(message (Hello 'Steven)
(you :wrote :eek:n '(Sat, 09 Dec 2006 20:02:06 +1100))
(

SD> It is a good thing that when Fred decides to stop contributing to an
SD> open source project (or leave the company), other people can read his
SD> code without having to learn his Uber-Special Custom Macro Extended
SD> Language. Even if Fred's USCMEL ran 35% faster (and thus saved an
SD> entire four seconds on an average run with typical data!) the five or
SD> six weeks of reduced programmer productivity when somebody else has to
SD> maintain his code outweighs that.

have you ever faced this problem?
it's not a problem at all.

and it's possible to write unmaintanable code in any langauge. for example,
Bram Cohen's BitTorrent (written in Python)
has a constructors with 21 positional parameters

class Rerequester:
def __init__(self, url, interval, sched, howmany, minpeers,
connect, externalsched, amount_left, up, down,
port, ip, myid, infohash, timeout, errorfunc, maxpeers,
doneflag,
upratefunc, downratefunc, ever_got_incoming):

and here's how it called:

rerequest = Rerequester(response['announce'],
config['rerequest_interval'],
rawserver.add_task, connecter.how_many_connections,
config['min_peers'], encoder.start_connection,
rawserver.add_task, storagewrapper.get_amount_left,
upmeasure.get_total, downmeasure.get_total, listen_port,
config['ip'], myid, infohash, config['http_timeout'], errorfunc,
config['max_initiate'], doneflag, upmeasure.get_rate,
downmeasure.get_rate,
encoder.ever_got_incoming)

i think it might be a bit hard to non-autist to remember order of parameters
in such constructor. (and it's not the only one such place in BitTorrent)
and that guy teaches us how to write maintanable code!
http://advogato.org/article/258.html

thus, you don't need macros to write unmaintanable code -- functions are
enough for this. and with macros you can make it more maintanable, actually


)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"People who lust for the Feel of keys on their fingertips (c) Inity")
 
J

Jon Harrop

Steven said:
Anything any language can do is possible in any other language

Not true. Concurrency, for example.
Lisp developers so often gloss over that: "Oh,
feature X is *easy*, I could write it in a couple of macros. Two or three.
Maybe thirty. Or forty, max. And they would work the right way first time.
No, I haven't actually done it myself. But I'm sure I could do it, easy."

True.
 
A

Andrea Griffini

Alex Mizrahi wrote:

....
so we can see PyDict access. moreover, it's inlined, since it's very
performance-critical function.
but even inlined PyDict access is not fast at all. ma_lookup is a long and
hairy function containing the loop.

I once had a crazy idea about the lookup speed problem;
can't the lookup result be cached in the bytecode ?
I am thinking to something like saving a naked pointer
to the value together with a timestamp of the dictionary.
With timestamp I mean an integer (may be 64 bit) that is
incremented and stamped in the dictionary every time
the dictionary is modified; this counter can be
shared among all dictionaries. The use of a naked pointer
would be IMO safe because to invalidate the object you
would also need to touch the dictionary.

Using this approach the lookup for a constant
string could be

if (bytecode_timestamp == dict->timestamp)
{
// just use the stored result
}
else
{
// do standard lookup and store
// result and dict->timestamp
}

I'd expect that this would be a big win for a lot of
lookup as the problem with python speed is the *potential*
dynamism... hopefully people don't keep changing what
math.sin is during the execution so the vast majority of
lookups at module level will find the timestamp being valid.
This invalidation is not "optimal" as changing math.sin
would also invalidate any lookup on math, but IMO a lot
of lookups happen in *fixed* dictionaries and the the
overhead of checking the cached result first should be
small. What it would break is code that actually
dynamically changes the string being looked up in the
dictionary in the bytecode, but I hope those places
are few if the exist at all.

Is this worth investigation or it has already
been suggested/tried ?

Andrea
 
S

Steven D'Aprano

you have an expression 3 + 4 which yields 7.
you have an expression 4 * 1 which yields 4.
if you paste 3 + 4 in place of 1, you'll have 4 * 3 + 4 = 16. as we know, *
is commutative, but 3 + 4 * 4 = 19.
so result depends on implicit operator precendence rules.

in lisp, if you paste (+ 3 4) in (* 4 1), you'll have (* 4 (+ 3 4)), and it
does not depend on order of operands, (* (+ 3 4) 4) yields same results. you
do not have to remember precendence rules

Speaking as somebody who programmed in FORTH for a while, that doesn't
impress me much. Prefix/postfix notation is, generally speaking, more of a
pain in the rear end than it is worth, even if it saves you a tiny bit of
thought when pasting code.

Except, of course, it doesn't really save you any thought. You can't just
expect to paste an expression randomly into another expression and have it
do the right thing. Oh, it will compile all right. But it won't do the
right thing! Since you -- not the compiler -- have to understand the
semantics of what you are pasting where, and paste the right expression
into the right place, the effort saved by not using infix notation is less
than the effort needed to mentally translate between prefix and infix.

If you are one of those people who actually think in prefix notation, then
I raise my hat to you while backing away slowly.

(And actually, I'll give you one point: I've been hitting my head against
a brick wall over a particular problem, and I think your email just gave
me a clue how to solve it. Sometimes prefix/postfix notation is the right
tool for the job. See, I can learn from Lisp.)
 
A

Alex Mizrahi

(message (Hello 'Ken)
(you :wrote :eek:n '(Sat, 09 Dec 2006 04:26:02 -0500))
(

KT> keep the Pythonistas from straying. But you have an excuse: Lispniks
KT> always /talk/ about macros giving us the ability to create a DSL. But
KT> no one does. :)

certainly there's no reason to make a new DSL each day, but sometimes they
are good.
i've recently posted an example of DSL to do queries to RDF data base -- i
find it's very helpful.

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"People who lust for the Feel of keys on their fingertips (c) Inity")
 
J

Jon Harrop

Mark said:
How do you compare Python to Lisp? What specific advantages do you
think that one has over the other?

Note I'm not a Python person and I have no axes to grind here. This is
just a question for my general education.

From my point of view as neither a Lisp nor Python user:

Lisp has some interesting features that separate it from many other
programming languages. In contrast, there is nothing interesting about the
Python language, AFAIK.

Lisp is fairly elegant in the way that it facilitates different programming
paradigms. Python is inelegant.

Lisp is likely to be faster.

Lisp is more mature but Python is (currently) more popular.

I think that people who know more languages and more about programming will
be much more inclined to use Lisp than Python. Look at the contents of the
newsgroups for example, c.l.l has a thread on memoization whereas c.l.p has
a thread about wrestling oiled pigs.
 
P

Paul Rubin

Alex Mizrahi said:
we can implement Scheme's call-with-current-continuation first :)
it's relatively easy -- just a code walker that coverts everyting into CPS.

It's not enough to convert to CPS, you have to be able to actually
save the continuation when you switch to another one, so you can go
back to the first one later. Maybe I'm missing something but I just
don't see how to do that in the Lisp execution model. I guess you
could write an interpreter in Lisp that simulates it all, but it might
as well be a Python interpreter ;-).
 
P

Pascal Costanza

Paul said:
It's not enough to convert to CPS, you have to be able to actually
save the continuation when you switch to another one, so you can go
back to the first one later.

You get this for free once your program is in CPS. (This is true for any
language, btw. It's just that it's easier to abstract away from the
details of CPS in Lisp.)


Pascal
 
A

Alex Mizrahi

(message (Hello 'Andrea)
(you :wrote :eek:n '(Sat, 09 Dec 2006 11:08:34 +0100))
(

??>> so we can see PyDict access. moreover, it's inlined, since it's very
??>> performance-critical function.
??>> but even inlined PyDict access is not fast at all. ma_lookup is a long
??>> and hairy function containing the loop.

AG> I once had a crazy idea about the lookup speed problem;
AG> can't the lookup result be cached in the bytecode ?

actually i don't see any reason why lookup is needed at all.
i think you can use approach similar to Lisp Symbols in Python, so it's
implementation slow, not the language.
there are some subtle differences -- for example, if you del global binding,
function gets undefined, but in Lisp uninterning does not invalidate code
that uses that symbols. but you can easily override this invalidating the
symbol bindings.

i think symbols can be implemented just as cache you suggest, but without
need of timestamp, but with additional indirection.
you should associate a SYMBOL with each entry in the globals dict, however
those SYMBOL lifetime should be managed independently (lifetime management
is one of difficulties, but i think not non-solvable). once you need to
cache lookup, you cache a SYMBOL pointer.
then you just get symbol's value on firther lookups. if dict gets update, it
should update all symbols associated with it. if entry (or whole dict) is
deleted, it should invalidate all symbols, so accessing them will produce
error, but it should not delete symbols.
you can resolve symbols not on first access, but during the read operation
(when bytecode is produced), as it's done in Lisp.

however, it's only applicable to LOAD_GLOBAL and STORE_GLOBAL, i think it
won't be possible to optimize STORE_ATTR that way without changing
semantics.

by the way, maybe some optimizations are already implemented in Psyco?
it's Python JIT with profile-guided type optimization, but i don't know how
it deals with lookups..

)
(With-best-regards '(Alex Mizrahi) :aka 'killer_storm)
"People who lust for the Feel of keys on their fingertips (c) Inity")
 

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,685
Members
48,796
Latest member
Greg L.

Latest Threads

Top