

Lisp vs Python - gnosis
http://amitp.blogspot.com/2007/04/lisp-vs-python-syntax.html

======
neutronicus
_It's easy to read. You can determine how to interpret something—a string, a
list, a function call, a definition—just by looking at the code locally._

I actually find that Lisp scores some local-readability points over Python in
a few cases, mainly because Lisp is more explicit about scope, e.g.

    
    
        (let ((a (something))
              (b (other-thing))
          (frobnicate a b))
        ...other code
    

versus

    
    
        a = something()
        b = other_thing()
        frobnicate(a, b)
        ...other code
    

is more explicit about the intended lifetimes and consumers of the data "a"
and "b". Lisp also has the "with a = (blah)" keyword for "loop" so the
assignment can be made specifically in the context of loop preparation.

All in all I'm actually happier with my ability to signal my intent (and gauge
other programmers' intent) in Lisp than I am in Python.

~~~
Locke1689
Python is explicit about scope by using the same indentation block. I can
glance at python code and immediately tell what the scope is because it's in
the same indentation block (removing for the moment things like the global
keyword, which tend not to be a problem in practice).

~~~
neutronicus
Can you increase indentation arbitrarily, though? Like, say I want to
initialize some data for a loop, that I wouldn't need later, could I:

    
    
        main_scope_stuff()
            c = create_context()
            for foo in foos:
                do_something(foo, c)
                do_other_thing(foo, c)
            accumulate(foos, c)
        more_main_scope_stuff()
    

to emphasize that c was only important to the loop and the call to
"accumulate"?

~~~
StavrosK
Yes:

    
    
        main_scope_stuff()
        with create_context() as c:
            for foo in foos:
                do_something(foo, c)
                do_other_thing(foo, c)
            accumulate(foos, c)
        more_main_scope_stuff()
    

That signifies that "c" isn't supposed to be available after scope of the
"with" statement. The variable will still be bound, but conceptually it's
unavailable, since it's not supposed to be used.

Also, the object in create_context() needs to support being called like that
(i.e. it should contain __enter__ and __exit__ methods).

~~~
euccastro
> The variable will still be bound, but conceptually it's unavailable, since
> it's not supposed to be used.

Which, in all fairness, is the same as saying that this does not indeed create
a new scope. If you're going to rely on convention, you might as well dispense
with the 'with' statement at all and use blank lines.

An explicit and safe (if not very elegant) way to put a variable out of scope
is just deleting it after you're done with it:

    
    
       main_scope_stuff()
       if True:  # Let's say we _really_ want indentation here..
           c = create_context()
           for foo in foos:
               do_something(foo, c)
               do_other_thing(foo, c)
           accumulate(foos, c)
           del c
        # something_or_other(c)  <--- raises UnboundLocalError
        more_main_scope_stuff()
    

That said, if it got to the point of going to this trouble I'd just make a
nested function instead.

------
stcredzero
_The trouble is that you can't easily tell just by looking at (f x) how to
interpret it._

Every serious development group is going to need a few patterns and coding
standards, because newsflash: _no language is perfect_. All languages _need to
be used properly_.

If you have good standards for naming, then a simple search should tell you
exactly how to read "(f x)" in less than a second. The same goes for Smalltalk
method names. If an extremely important method in your application code gets
named "add:" well, guess what, there are already base class methods named
"add:"! This means your senders searches and implementor searches -- which are
fundamental to the way lots of expert Smalltalkers code -- are going to be
polluted.

Also, the same thing can apply to function names in Python!

In dynamic languages, good programmers know names are very important. Before a
good, experienced dynamic language programmer names something "foo", they
first search for _foo_! They search for things that might be confused with
foo! Programmers of dynamic languages who don't do this are either ignorant or
inconsiderate. This post doesn't strike me as written by someone experienced
or knowledgeable with development in dynamic languages. (And if he's
experienced, he's had the bad luck of having been mentored in short-sighted
groups.)

 _There's a lot of repetition and many times it's downright verbose. But where
Lisp is nice to write and hard to read, Python makes the opposite tradeoff.
It's easy to read. You can determine how to interpret something—a string, a
list, a function call, a definition—just by looking at the code locally._

This post takes no account of Don't Repeat Yourself and is seemingly written
with no foreknowledge of this idea. Curious, since Python is powerful enough
to get close to the ideal of _Don't Repeat Yourself_.

Strategy for creating a blog post that looks analytical and sounds good -- but
only to sophomore programmers:

1) Pick a certain aspect Y of language X which can be abused

2) Give a detailed analysis for why Y is bad

3) Completely leave out any mention of known best practices for addressing Y

4) Compare to a more popular language Z. Leave out any mention of Y applied to
Z.

This way, you can get some attention from sophomore programmers and provide
them a way to feel better by giving them a good sounding reason to avoid
studying X.

Really, this kind of sophistry is so common, there should be a name for it!
(Actually, there is a way that Lisp can be easy to write and hard to read, but
that has to do with macro expansion. But the tendency is for those with
substantive Lisp experience to know about things like that.)

EDIT: To avoid distracting "ad hominem" nonsense. Note the argument is
completely the same.

EDIT: Got read/write mixed up.

~~~
nostrademons
"What this actually reveals is that this poster is used to code bases not
advanced to the point of Don't Repeat Yourself. He may not even be at an
advanced enough level to know about this idea or fully appreciate it. Python
is powerful enough to get close to the ideal of Don't Repeat Yourself.
Evidently, this poster has no clue."

You realize that this poster is Google employee #7, responsible for "Don't be
evil" (along with Paul Buchheit), and wrote the first prototype for Google
Instant back in 1999?

Something else to keep in mind, from the article:

"When I read debates online, I have a bias towards the people who view these
things as tradeoffs and a bias against the people who say there's only one
right answer and everyone else is stupid or clueless ... When you're in a
debate, consider that the other person might not be stupid, and there might be
good reasons for his or her choices."

~~~
stcredzero
_You realize that this poster is Google employee #7, responsible for "Don't be
evil" (along with Paul Buchheit), and wrote the first prototype for Google
Instant back in 1999?_

Why, are you _arguing from authority_ on his behalf? In that case, I say he
should know better! (That's a stand worth burning some karma on!)

tl;dr - If you are going to criticize language X, first take the time to learn
the best practices of language X. If you don't, you're just creating more
clueless noise of a kind which already exists online by the ton. Also, it
seems quite lazy. All you have to do is get on IRC and ask, "Hey, I've been
playing around with X. What about when this happens?" You might even learn
something this way.

EDIT: Google employee #7? Makes me wonder if dozens of Google programmers have
said something like, "Arrrgh, why did he have to name it _that_!? Did Amit
write that code?"

~~~
nostrademons
No, I'm arguing against the ad hominem that forms the second half of your
original post.

The first half of your post was great. It addresses the arguments directly,
and I completely agree. In the second half, you then go on to make assumptions
about the programmer's experience and aptitude - assumptions that I've shown
are quite false. If you want to debate the merits of an argument, debate the
merits of an argument, don't assume that everyone who argues with you is
stupid. There's a very good chance that they aren't, and then you look like a
fool.

Edit: On a regular basis, I whine about Craig Silverstein or Jeff Dean or
Marissa Mayer's code. "Why did s/he have to write it like that? It's cost us
untold thousands of hours in coding around their nasty hacks and poor design
decisions." However, I recognize the context they were operating in: they were
a tiny startup of about 20 people, trying to organize the world's information.
They were working basically round the clock, and they changed the world.
Sometimes, building something that works for users and won't crash your
datacenter is worth the price of maintainability. You have to survive before
you have the luxury of hiring people that will call you stupid.

Tradeoffs. They come with every hard problem.

~~~
stcredzero
_"Why did s/he have to write it like that? It's cost us untold thousands of
hours in coding around their nasty hacks and poor design decisions." However,
I recognize the context they were operating in: they were a tiny startup of
about 20 people, trying to organize the world's information._

Yes, but if you're lucky enough to be Google Employee #7, you should now have
time to do things like actually do your homework before you post about Lisp.

Just because you're big company coder [single digit] doesn't mean you're
necessarily an expert. I actually _expect_ a lot of startup code to be half-
baked. Large swathes of the Smalltalk base image code is still egregious, and
people have had _decades_ to do something about it and haven't. There were
also lots of sophmoric Java API decisions when it first came out.

I don't mistake seniority for expertise, and seniority or friendship with you
or anyone in particular doesn't earn anyone a pass with _me_.

If he knows about but doesn't subscribe to Don't Repeat Yourself then he does
a good job of sounding like he just doesn't have a clue and is in the practice
of writing lots of boilerplate code. To me, that is a merit of a programming
language argument. (In this case demerit.)

Please explain how you can read this post and think the author has a clue
about careful name selection? Basically, his major point is predicated on
clueless naming in dynamic languages.

------
lispm
(f x) is not different things. It is just that the meaning of (f x) depends on
a context and is only meaningful in a context.

    
    
        (f x) alone is a call, a macro or function.
    
        (let ((f x)) ...) , here (f x) is an item in a binding list.
    

What a Lisp programmer sees is a pattern introduced by LET:

    
    
        LET ((var value)*) body
    

There are a handful of basic macro code structure patterns. Once they are
learned and attached to the symbol of the corresponding macro, the
understanding of code is much improved.

Reading Lisp code requires to identify the visual marker - the symbol in
front. Depending on that there are code patterns that can be visually
destructured: lists, property lists, assoc lists, lambda lists, binding lists,
...

In other languages the visual markers are supported by different characters.
In Lisp it is a symbol.

    
    
        {
           a := b + x;
        }
    

vs.

    
    
        (progn
          (setq a (+ b x)))
    

As a Lisp programmer I'm trained to see the PROGN, not the parentheses.

It is a bit like being afraid of riding a bicycle for the first time. How can
one move forward and balance at the same time? Difficult. Once learned it is
simple and hard to unlearn.

How can one read the various Lisp code patterns? Once you have learned them it
is easy and hard to unlearn.

------
thurn
Many of the design choices in Clojure are aimed at resolving precisely these
concerns. Quoted lists and literal lists are of course usually written '(f x)
in most dialects, and Clojure uses [f x] for function arguments and binding
lists.

The only concern that remains is that (f x) could be a function call or a
macro invokation. This is entirely by design, and is in fact a core idea of
lisp that lets you build your own constructs with the same syntax as the
language's native ones.

------
jhuni
_Lisp seems to be optimized for writing code; Python seems to be optimized for
reading it._

You know what is truly optimized for writing code and not reading it?
Mathematical notation - and for good reason, most mathematical equations are
written on paper. Mathematicians use one letter variable names like (i, j, n,
m, x, y, z), various Greek letters, infix notation, and other things that
often makes mathematical equations incredibly hard to read, but easier to jot
down on paper.

Almost all programming languages, such as C, Java, Python, and Perl are
influenced by this notation that although it was easy to write on paper, is
not as easy to read and not nearly as easy to parse.

Lisp is not nearly as easy to write on paper - and it often is harder to type
- however - for any experienced Lisper it is the most readable programming
language there is, hands down, because once you use it for long enough the
parenthesis fade away and you can write/read code without the mental overhead
that is entailed by parsing.

------
jfm3
"Sometimes (f x) is [one of 6 things]"

I don't mean to be the next snarky Lisp guy in the room, but I don't see the
difference between reading

    
    
        (if (= a 3) ...)
    

and thinking "that's a special form and a predicate", and reading

    
    
        if a == 3:
            ...
    

and thinking "that's a special form and a predicate". In either case there's
"if". Writing "(f x) could be anything" is a straw man. As soon as you replace
"f" with "if" the argument falls apart.

(No, you can't name a function "if" in Common Lisp. Go ahead, try it.)

~~~
Jach
Ah, but you can in Scheme. :)

    
    
        guile> (define if (lambda (. args) (display "Haha!") (newline)))
        guile> (if (= 3 4) 't 'f)
        Haha!

~~~
jfm3
Cute! And you're right! Scheme should be kept away from people who are prone
to hurt themselves, as well as nail clippers and letter openers. Too
dangerous!

:)

------
j_baker
> Lisp seems to be optimized for writing code; Python seems to be optimized
> for reading it.

Urgh... A hundred times no. I really do appreciate that the author seems to
have given Lisp a try. But this is simply wrong.

Why does Lisp provide macros? Because they make code easier to read. I say
stop focusing on what language abstraction (f x) represents and focus on
figuring out what the code is _doing_.

Oh well, at least he didn't complain about lisp having too many parenthesis.

~~~
lwat
_Why does Lisp provide macros? Because they make code easier to read._

To the person writing the code, yes. To the next coder maintaining the code,
not likely.

~~~
pavelludiq
Do you have any data(even if anecdotal) to back up your claim? My experience
is that macros don't obfuscate code at all(unless misused, I'm talking about
idiomatic use of course). If i have the code loaded i just type (documentation
'macro-or-function-name 'function) to get its docstring. Slime can also show
me its documentation and its argument list. Not to mention that a good
macro(like any other well written code) is obvious. On the internet Lisp is
the most unmaintainable language, in practice its pretty maintainable.

~~~
lwat
Here's an anecdote from the other day:

<http://news.ycombinator.com/item?id=2240461>

On another note, why do _you_ think lisp isn't more widely used?

~~~
cesarm
<http://en.wikipedia.org/wiki/Worse_is_better>

------
intellectronica
Saying that LISP is for writing code and Python is for reading code is like
saying that cars are for travelling uphill and bicycles are for travelling
downhill.

~~~
mkramlich
you're right though, even if you thought you were making fun of it

because that was the OA's point: that if a developer wanted to optimize for
going uphill, choose a car rather than a bicycle. downhill? bicycle will do
that with less resources, waste, cost, pollution, volume passed through, etc.
pick which is better given the context and optimization goals.

------
comex
> Python on the other hand has no macros and doesn't give you much to write
> concise, abstract, elegant code. There's a lot of repetition and many times
> it's downright verbose. But where Lisp is nice to write and hard to read,
> Python makes the opposite tradeoff. It's easy to read. You can determine how
> to interpret something—a string, a list, a function call, a definition—just
> by looking at the code locally.

This is a great quote. If you replace Python with C and Lisp with C++, it's
exactly what I've been trying to articulate (as a C fan) for some time now. In
fact, I think it's much more applicable to C than to Python...

------
mark_l_watson
I also agree with the author's post script. Both are good languages that while
overlapping a bit are probably best for different types of projects. I use
Python when I need an existing library like NLTK or simple web apps hosted on
AppEngine, Common Lisp for research, some semantic web stuff, and general NLP.
I use Gambit-C Scheme mostly to write small natively compiled command line
utilities (mostly NLP stuff).

It is all about getting stuff done, not insisting on using a favorite
language, framework, or platform. Solving problems is what is fun and
rewarding, not getting stuck on particular technologies.

------
mitko
This is similar to the way I compare Python and Lisp:

Common ground: first class functions, a lot of flexibility, closures

Lisp: macros, performance?

Python: easy to learn by example, readability, batteries included

I use Python day-to-day as I don't really need a macro system that much. I
also disagree that Lisp is more elegant - beauty is in the eye of the
observer.

Edit: formatting.

~~~
euccastro
Beauty is subjective; elegance less so. Python is the language I've used and
use the most, and I'll easily concede that Scheme is more elegant than almost
anything else I've seen, and Python hardly even compares. Metaclasses,
decorators, 'for'/'while'/'with' statements, etc. etc. In Python they are all
good, practical, powerful ideas. In Scheme they're just unnecessary. Scheme is
more elegant because it's been a priority in its design. Python aims to be
more practical, beginner-friendly and straightforward.

~~~
eru
Try Haskell. It has a strange mix of beautiful elegant core, and lots of
(useful) syntactic sugar added on top.

~~~
euccastro
I did try it, but not enough, I guess, to tell the beautiful core you talk
about from the rest. So Haskell seemed to me full of elegant ideas, yet less
elegant, as a whole, than Scheme.

~~~
Locke1689
Which Scheme are you talking about? I find the awful type system of base R6RS
to be extremely inelegant. Contracts and strong typing make formal reasoning
about programs far more powerful, hence more modern Scheme-style languages
like Racket.

~~~
euccastro
Good catch. I was talking about R5RS and earlier.

------
DrJokepu
I don't know about that. Say what you want about Emacs (personally I'm not a
huge fan) it does a pretty good job at syntax highlighting and auto-formatting
Lisp source. I never had problems with reading well-written Lisp code in
Emacs.

------
bradleyland
I especially like the author's post script. Fortunately, this appears to be
the prevailing attitude at HN.

------
Someone
If that argument is valid, why not go a step further to, say, early basic
implementations? Back in the day, there was no discussion possible whether 'x'
was an integer, float, or string. If it were a string, it would be x$, if it
were an integer, i%.

I do not think his argument is valid. In both languages, once a program is
large enough for any syntactic ambiguity to make a difference, there will be
more than enough semantic clues to help solve the syntactic disambiguaties.

In other words: yes, one can write really hard to understand Lisp, where (f x)
has six meanings in one line, but outside of obfuscated code contests,
programmers just do not write such code.

Edit: the semantic clues I refer to are those implied by function names, names
of data types, etc.

------
gnosis
Related discussion:

[http://www.reddit.com/r/programming/comments/1ka0j/lisp_vs_p...](http://www.reddit.com/r/programming/comments/1ka0j/lisp_vs_python_syntax)

------
softbuilder
>Python on the other hand has no macros

I challenge this. I won't pretend that Python has full macro equivalence, but
decorators are damned close.

~~~
jfm3
Decorators are ways to wrap function definitions with code that gets executed
at the time the function is defined. Commonly they add stuff that happens
every time the function is called.

This is certainly something you can do with macros, but it's not even really
close to the full semantics. Macros allow you to add new special forms to the
language. If Python had macros, for example, the new `with` statements would
have been a five line addition to the standard libraries.

I really don't see it as "damned close" at all.

~~~
softbuilder
"Damned close" perhaps should be qualified as from the point of view of a
Pythonista, given the ideals of that approach to programming. I took issue
with the specific point that Python "has no macros", and then I qualified it,
knowing full well that Lisp macros do more.

Decorators ultimately result in a function that is a substitute for another
function. That function can have all sorts of wonderful runtime behavior,
including access to arguments, the call stack, and other functions. Dismissing
them as "adding stuff that happens every time the function is called" misses
an entire dimension of their utility.

~~~
euccastro
Sorry to be blunt, but it's only "damned close" from the point of view of
someone that doesn't understand either decorators or macros.

Decorators are syntactic sugar for a particular mix of assignment and function
call. They're just there so you only need to write the name of the decorated
object (say, function or class) once rather than three times. For example

    
    
      @some_deco
      def f(a, b):
          return a+b
    

Is exactly the same as

    
    
      def f(a, b):
          return a+b
      f = some_deco(f)
    

Let me repeat: decorators only save you writing the name of the function two
additional times. Which is nice and worth it, but not groundbreaking, and,
more to the point, has _nothing whatsoever_ to do with macros (except perhaps
that in Python you couldn't do it without macros, but that's besides the
point).

The equivalent to decorators in Lisp is just normal higher order programming
(functions taking functions as arguments, and/or returning functions), with no
particular syntax.

    
    
      (define f 
        (some-deco 
          (lambda (a b) (+ a b))))
    

It would be bad form to use a macro for something like this, which is firmly
within the domain of normal function definitions and calls.

(P.S.: For short functions like this, you could have said, in Python:

f = some_deco(lambda x, y: x + y)

But this breaks for multiline functions and class definitions.)

~~~
softbuilder
You're conflating decorator syntax, which I'm not discussing, and decorator
functionality, which I am discussing. If there was no syntax for decorators
and the only way to decorate a function was to use assignment/composition like
in your example, I would still consider that to be a decorator.

A macro is going to be evaluated at compile time and will result in a matching
pattern (often something that is structured like a function call) being
expanded into some other construct that will be evaluated at runtime.

A decorator is going to be evaluated at compile time and will result in a
decorated function being "expanded" into another function which will be
evaluated at runtime.

No, they aren't the same thing. How could they be? The languages are
different. But there is an underlying affinity.

~~~
euccastro
> You're conflating decorator syntax, which I'm not discussing, and decorator
> functionality, which I am discussing.

Guilty as charged. I admit I thought you were talking about decorator syntax.
Because decorator-the-technique makes even less sense as an alternative to
macros.

Decoration is a technique of higher order programming (taking functions as
arguments and/or returning them). The fact that you can use it at "compile
time" (whatever that means in Python) is a property of the dynamic nature of
the language.

Yes, that stuff is powerful. But Scheme has all that, and its designers still
saw the need to add macros, mostly to do the things that you _can't_ do by
executing higher order functions at "compile time".

Macros are not essentially about doing stuff at compile time; they're about
messing with the very syntax of the language.

My point is not just that decorators and macros aren't the same thing; it's
that macros are meant explicitly to do what decorators can't.

I guess most Python programmers don't miss macros simply because they've never
seen the need for them in first place, not because Python has any replacement.
The closest alternative that Python has to macros would be eval.

In my opinion, what ultimately allows most Python programmers who have been
exposed to macros to almost never miss them is the syntactic sugar that the
Python authors keep (judiciously) adding every release.

------
steve918
This post has some interesting points, but I would really appreciate some
concrete code samples.

------
sigzero
Without reading the article, can this be summed up as "People actually _use_
Python"?

~~~
RiderOfGiraffes
No. It's something entirely different, which you would know if you had read,
or even just skimmed, it.

