merits of Lisp vs Python

J

jayessay

Paul Rubin said:
You'd just write a function. Python's expression syntax is comparable
to a Lisp reader (you can have nested values of mixed types etc.) so
you can use Python expressions to initialize pretty much anything.

Nah, that won't work. He's defining a set of methods _declaratively_
via a high level description.


/Jon
 
W

William James

André Thieme said:
That you see it this way is normal.
A BASIC programmer would tell you the same thing. He can show you
solutions that don't use classes, methods or functions.
Some sweet gotos and gosubs are enough.
The idea is that macros save you tokens and allow you to experess
in a clearer way what you want to do. But in no case one "needs"
them to solve a programming problem. All what Kenny showed could
be done without his macro. It would just be a bit more complicated
and the resulting code wouldn't look good.



He wouldn't have to write the full expansion. With functional
programming he could also solve it, but then he would need
a funcall here, a lambda there. And his code would not look
so understandable anymore, because it is filled up with some
low level details.


I will take one of the first macro examples form "On Lisp".
Let's say for some reason you want to analyse some numbers
and do something depending on their sign. We want a function
"numeric if":

def nif(num, pos, zero, neg):
if num > 0:
return pos
else:
if num == 0:
return zero
else:
return neg


In Lisp very similar:
(defun nif (num pos zero neg)
(case (truncate (signum num))
(1 pos)
(0 zero)
(-1 neg)))


Now one example Graham gives is:
(mapcar #'(lambda (x)
(nif x 'p 'z 'n))
'(0 2.5 -8))

which results in the list (Z P N).
You can do the same thing in Python.
But it gets hairier if we want to make function calls that
have side effects.
Let me add these three functions:

(defun p ()
(print "very positive")
"positive")

(defun z ()
(print "no no")
"zero")

(defun n ()
(print "very negative")
"negative")


And now see what happens:

CL-USER> (mapcar #'(lambda (x)
(nif x (p) (z) (n)))
'(0 2.5 -8))

"very positive"
"no no"
"very negative"
"very positive"
"no no"
"very negative"
"very positive"
"no no"
"very negative"
("zero" "positive" "negative")

The messages were printed in each case.
To stop that I need lazy evaluation:
CL-USER> (mapcar #'(lambda (x)
(funcall
(nif x
#'(lambda () (p))
#'(lambda () (z))
#'(lambda () (n)))))
'(0 2.5 -8))

"no no"
"very positive"
"very negative"
("zero" "positive" "negative")


I put the calls to the functions p, z and n into a function object.
In some languages it would look a bit cleaner, for example Ruby.
They have a single name space and don't need funcall and lambda is
shorter. But still, we need to add several tokens. Maybe Haskell has
built in support for that.

def p
puts "very positive"
"positive"
end
def z
puts "no no"
"zero"
end
def n
puts "very negative"
"negative"
end
def nif num, pos, zero, neg
send( num>0 ? pos : (num==0 ? zero : neg) )
end

[0, 2.5, -8].map{|x| nif x, :p, :z, :n}

### Another way #####

p = proc {
puts "very positive"
"positive" }
z = proc {
puts "no no"
"zero" }
n = proc {
puts "very negative"
"negative" }
def nif num, pos, zero, neg
( num>0 ? pos : (num==0 ? zero : neg) ).call
end

[0, 2.5, -8].map{|x| nif x, p, z, n}
 
?

=?ISO-8859-15?Q?Andr=E9_Thieme?=

Neil said:
That's not a real difficulty, is it?

CL-USER> (mapcar #'(lambda (x)
(funcall (nif x p z n)))
'(0 2.5 -8))

Didn't you forget the #' before p, z and n?

That's the part that Python doesn't naturally provide, and I
believe, Python programmers don't usually want.

Then I have the solution:
they can build their own functional if.
Instead of

if x > 10:
do()
something()
else:
foo()


They can say:

def blah1():
do()
something()

def blah2():
foo()


pythonIF(x > 10,
# then
blah1,
# else
blah)



I personally thought the first version was the Python way.
Now let's admit that

nif number:
pos_then:
p()
zero_then:
z()
neg_then:
n()

is also the Python way. With just one problem: they can't have it.


André
--
 
?

=?ISO-8859-15?Q?Andr=E9_Thieme?=

William James schrieb:

I suppose that is Ruby code.
So my statement was correct when I said:
"In some languages it would look a bit cleaner, for example Ruby."

This is because it has a minimal syntax for "lambda".

def p
puts "very positive"
"positive"
end
def z
puts "no no"
"zero"
end
def n
puts "very negative"
"negative"
end
def nif num, pos, zero, neg
send( num>0 ? pos : (num==0 ? zero : neg) )
end

This code would work

[0, 2.5, -8].map{|x| nif x, :p, :z, :n}

See how you move away from the idea of an if.
Here you need to say :p, :z, :n.
That is not much better as Lisps #'

### Another way #####

p = proc {
puts "very positive"
"positive" }
z = proc {
puts "no no"
"zero" }
n = proc {
puts "very negative"
"negative" }
def nif num, pos, zero, neg
( num>0 ? pos : (num==0 ? zero : neg) ).call
end

[0, 2.5, -8].map{|x| nif x, p, z, n}


But will this version also work this way:
[0, 2.5, -8].map{|x| nif x, "p", "z", "n"}

You can't express it like an "if" in Ruby.
In Lisp it is like an IF and represents exactly what
we think.
IF in Lisp:
(if expr
(then-part)
(else-part))

nif in Lisp:
(nif expr
(positive-part)
(zero-part)
(negative-part))

It looks as if it were a construct directly built into Lisp.
If one wants one could even add some additional syntax, so
that it looks like:
(nif expr
positive:
(foo1)
(foo2)
zero:
(foo3)
negative:
(foo4))

If you regard that idea nonsense then I suggest you to not
use Rubys if-statement anymore. But instead program your
own version "RubyIF" so that in future you have to pass all code
inside blocks to your RubyIF function. If you *really* think
that the Lisp savings are not worth it, then you would begin
with my suggestion today.


André
--
 
?

=?ISO-8859-15?Q?Andr=E9_Thieme?=

William said:
def nif num, pos, zero, neg
send( num>0 ? pos : (num==0 ? zero : neg) )
end

btw, your nif body is built out of 13 tokens, so more
complicated than the Python version.


André
--
 
M

metawilm

Paul said:
I thought it was of some interest though I'm a little surprise by the
choice of CL rather than Scheme as a target.

In many aspects Python is a subset of CL. In CLPython, exceptions are
Lisp conditions with a custom metaclass (strictly spoken not portable
CL), Python (meta)classes are CLOS classes, methods are implemented
using hashtables and functions and CLOS generic functions, strings and
numbers have direct equivalents, hash tables implement dicts, Python
functions are funcallable CLOS instances, tuples and lists are Lisp
lists and vectors, generators are closures. Thus a great part of
implementing CLPython consisted of connecting already existing dots. I
found that quite amazing.
I'm still not sure about the mapping of Python strings to Lisp strings.
What happens with the following in CLPython?

a = 'hello'
a[0] = 'H' # attempt to change first letter to upper case

As CLPython mirrors Python semantics, this results in a TypeError. The
internal representation of an immutable Python string is a mutable Lisp
string, but there is no way you can actually modify it from within
CLPython.

Now in some cases modifying strings is dangerous (e.g. when stored in
an instance dict to represent an attribute), but in other cases it can
be completely safe. And modifying strings is a normal Lisp feature, so
it sounds strange to disallow it at all times. Maybe there should be a
way for the user to declare that he knows what he is doing, and does
not want the Python implementation to be "crippled" w.r.t. the host
language. Normally you start the CLPython interpreter using (repl);
maybe something like this would allow dangerous statements:

(let ((*strict-python-semantics* nil))
(repl))

Oh, and there is actually one way to modify Python strings in the
interpreter, namely using Lisp commands:

PYTHON(12): (repl)
[CLPython -- type `:q' to quit, `:help' for help]A NEW simple-string (3) "xbc" @ #x11548f6a
0-> The character #\x [#x0078]
1-> The character #\b [#x0062]
2-> The character #\c [#x0063]
[1i] PYTHON(13): :ptl
 
N

Neil Cerutti

In Lisp it is like an IF and represents exactly what we think.
IF in Lisp:
(if expr
(then-part)
(else-part))

nif in Lisp:
(nif expr
(positive-part)
(zero-part)
(negative-part))

It looks as if it were a construct directly built into Lisp. If
one wants one could even add some additional syntax, so that it
looks like:
(nif expr
positive:
(foo1)
(foo2)
zero:
(foo3)
negative:
(foo4))

If you regard that idea nonsense then I suggest you to not use
Rubys if-statement anymore. But instead program your own
version "RubyIF" so that in future you have to pass all code
inside blocks to your RubyIF function. If you *really* think
that the Lisp savings are not worth it, then you would begin
with my suggestion today.

I don't know how to build a house. It doesn't make me want to
live in a cave. ;-)
 
?

=?ISO-8859-1?Q?Andr=E9_Thieme?=

greg said:
(e-mail address removed) wrote:

As opposed to Lisp, where all you have to do is
use parentheses... oh, er...

Lisp has no parens. An editor could support a mode where code
is displayed in written in trees. There wouldn't be a single
paren.

I don't know about the other Pythonistas in this
discussion, but personally I do have experience with
Lisp, and I understand what you're saying. I have
nothing against Lisp parentheses, I just don't agree
that the Lisp way is superior to the Python way in
all respects, based on my experience with both.

Lisp is not superior in all respects. As soon you decide to use a
language you decide against other features.
Python is especially strong as a scripting language as that is the
domain of it.


André
--
 
W

William James

André Thieme said:
btw, your nif body is built out of 13 tokens, so more
complicated than the Python version.


André
--

def nif num, *args
send args[ 1 + (0 <=> num) ]
end
 
?

=?ISO-8859-1?Q?Andr=E9_Thieme?=

William said:
André Thieme said:
btw, your nif body is built out of 13 tokens, so more
complicated than the Python version.


André
--

def nif num, *args
send args[ 1 + (0 <=> num) ]
end


send
|
|
[ ]
/ \
/ \
/ \
args +
/ \
/ \
1 ()
|
|
<=>
/ \
/ \
0 num

Okay, 9. Now it is at the complexity level of the Lisp
and Python example.
But how would a call to nif look like?
I hope it doesn't involve an extra operator to group the
args into a list or tuple. That would add more complexity to
each call - so one should better have a slightly more complex
nif because that exists only one time.

And can this nif now handle any input values, such as strings
or function objects?

The <=> is called cmp in Python.
In Lisp it is called signum. The Lisp function has in general the
advantage that it keeps type information.
While Pythons cmp returns either -1, 0 or 1 the Lisp version can
also return -1.0 and 1.0 and also complex numbers:
(signum #C(10 4)) => #C(0.9284767 0.37139067)


André
--
 
W

William James

André Thieme said:
Paul said:
Oh come on, you have to count the parentheses too.

We could define hundreds of way how to count tokens.
But see the program as a tree:

nth
/ \
/ \
/ \
1+ list
| / | \
| / | \
truncate pos zero neg
|
|
signum
|
|
num

And I didn't count the indentation level and \n in Python code.
Why should I? They are editor commands.

Anyway, token count doesn't mean much, you have to instead go by the
user's cognitive effort in dealing with the prefix notation etc.,

How complicated ss it to say "cmp(a, b)" compared to "a cmp b"?
After a few days writing Lisp code your brain specializes on that
style. Arabian countries write from right to left. They seem to have
no problem to use what they know.
I admit that I am used to write +, -, * and / infix, because I were
trained in that thinking since day 1 in school.
In our example program there was only 1+ compared to ".. + 1" which
looks "alien" to untrained people.
If a program uses extensive math we can get syntax for Lisp that is
even more pleasant to read than Pythons:
17a + x³-x²


which isn't purely a matter of token count. And if (+ 2 3) were
really as easy to read as 2+3, mathematics would have been written
that way all along.

Mathmaticians invented prefix notation and they use it regularily.
They inventey operators that wrap around their arguments, as one can
see here: http://www.mathe-trainer.com/formel/?SN=&t=1

Or think of indices, etc.



Yes, that works. And that's why I wanted to make it a macro.
Now you are essentially doing what I did in my first Lisp version.
While this works it is farer away from what we think.

(nif 0 "p" "z" "n")
(nif 0 #'p #'z #'n)
are exactly what one thinks.
For this reason "if" is implemented as a keyword in Python.
It allows you to give code as a block instead of embedding it into
a lambda or a def (which even needs a name).


And we might go further (again with an easy Graham example).
See this typical pattern:

result = timeConsumingCalculation()
if result:
use(result)

We need this ugly temporary variable result to refer to it.
If we could use the anaphor[1] "it" that could make situations like
these more clean.

Imagine Python would have an "anaphoric if", "aif". Then:

aif timeConsumingCalculation():
use(it)

Many Lispers might have this as a macro in their toolbox after some
time of coding or after reading Graham.
A short three-liner in Lisp and I can really say:

(aif (timeConsumingCalculation)
(use it))

How would you solve this in Python?
You could embed it inside a lambda and must somehow make the
variable "it" visible in it, because in the context of aif this
"it" gets bound to the result.

In Ruby:

def aif val
yield val if val
end

def complex_calc n
if n % 2 == 1
n**n
else
nil
end
end

aif( complex_calc( 9 ) ) {|x| puts "hello, #{x}"}
aif( complex_calc( 8 ) ) {|x| puts "hello, #{x}"}

--- output -----
hello, 387420489
 
W

William James

André Thieme said:
William said:
André Thieme said:
William James schrieb:

def nif num, pos, zero, neg
send( num>0 ? pos : (num==0 ? zero : neg) )
end
btw, your nif body is built out of 13 tokens, so more
complicated than the Python version.


André
--

def nif num, *args
send args[ 1 + (0 <=> num) ]
end


send
|
|
[ ]
/ \
/ \
/ \
args +
/ \
/ \
1 ()
|
|
<=>
/ \
/ \
0 num

Okay, 9. Now it is at the complexity level of the Lisp
and Python example.
But how would a call to nif look like?

A call is unchanged:

[0, 2.5, -8].map{|x| nif x, :p, :z, :n}

I hope it doesn't involve an extra operator to group the
args into a list or tuple. That would add more complexity to
each call - so one should better have a slightly more complex
nif because that exists only one time.

And can this nif now handle any input values, such as strings
or function objects?

It handles only symbols.
I suppose this can handle anything ...

p = proc {
puts "very positive"
"positive" }
z = proc {
puts "no no"
"zero" }
n = proc {
puts "very negative"
"negative" }
def nif num, *args
args[ 1 + (0 <=> num) ].call
end

[0, 2.5, -8].map{|x| nif x, p, z, n}
puts [0, 2.5, -8].map{|x| nif x,
proc{'zero'}, proc{'plus'}, proc{'minus'}}

.... if you wrap everything in a proc (lambda).
 
?

=?ISO-8859-1?Q?Andr=E9_Thieme?=

> Okay, so it's certainly _doable_. I think the question is more if it's
> usable. Regular if:s don't like that in Ruby/Python/similar, and adding
> such a construct would mean extending the syntax.
>
> In Lisp, all (well, almost) functions are equal, so an (if ...) would
> look the same like the (aif ...). I think that's the point here.

Exactly that is the point.
*Of course* it is doable.
And what William shows us is maybe one of the best solutions
in Ruby. He used advanced abstractions that made it possible.
The thing with most (if not all) programming languages other than Lisp
is: these abstractions are leaky.
They have a low level knowledge leak. Users of aif need to know how to
pass in arguments. As you see he had do embed the code that needs execution
into a block (anon function) and then use this special syntax for x.


André
--
 
S

Slawomir Nowaczyk

On Fri, 15 Dec 2006 17:21:12 +0100

#> And we might go further (again with an easy Graham example).
#> See this typical pattern:
#>
#> result = timeConsumingCalculation()
#> if result:
#> use(result)
#>
#> We need this ugly temporary variable result to refer to it.
#> If we could use the anaphor[1] "it" that could make situations like
#> these more clean.
#>
#> Imagine Python would have an "anaphoric if", "aif". Then:
#>
#> aif timeConsumingCalculation():
#> use(it)

I would spell the above like this:

def timeConsumingCalculation():
pass
def useit(it):
pass

def aif(first,second):
res = first()
if res:
second(res)

aif(timeConsumingCalculation,useit)

Sure, it requires me to define function useit instead of embedding the
code in aif call, but that has never been a problem for me: in reality,
the code I would want to execute would be complex enough to warrant it's
own function anyway. Of course, YMMV.

--
Best wishes,
Slawomir Nowaczyk
( (e-mail address removed) )

Real programmers can write assembly code in any language.
 
W

William James

André Thieme said:
Exactly that is the point.
*Of course* it is doable.
And what William shows us is maybe one of the best solutions
in Ruby. He used advanced abstractions that made it possible.
The thing with most (if not all) programming languages other than Lisp
is: these abstractions are leaky.
They have a low level knowledge leak. Users of aif need to know how to
pass in arguments. As you see he had do embed the code that needs execution
into a block (anon function) and then use this special syntax for x.


André
--

I may have tried too hard. The simpler way is usually better.
No aif() is needed:

if x = complex_calc( 9 )
puts "hello, #{x}"
end
 
P

Paul Rubin

André Thieme said:
And I didn't count the indentation level and \n in Python code.
Why should I? They are editor commands.

No they're part of Python syntax. A change in indent level is
recognized by Python's lexical scanner as a token and you should count
it. You wouldn't count the indent and \n separately though.
How complicated ss it to say "cmp(a, b)" compared to "a cmp b"?

It gets worse when the expressions are nested.
Imagine Python would have an "anaphoric if", "aif". Then:

aif timeConsumingCalculation():
use(it)

Well, it's not really in the Pythonic style to add obscurity like
that, but you could certainly write something like:

def aif(func):
it = func()
if it: use(it)

aif(timeConsumingCalculation)
So in some languages that support functional programming one needs
to do extra work to get lazyness, while in Haskell one gets extra
work if one doesn't want it. But maybe someone can correct me here...
In Lisp one could build some features to get lazyness as well.

I think that is essentially correct. But it's not so easy to do extra
work to get Haskell-like laziness in languages that don't have it, not
because laziness itself is difficult, but non-lazy languages usually
end up giving up referential transparency.

http://lambda-the-ultimate.org/classic/message5750.html
 
P

Paul Rubin

André Thieme said:
Lisp has no parens. An editor could support a mode where code
is displayed in written in trees. There wouldn't be a single paren.

But there would be even more tokens, the lines going between the nodes
in the trees, for example.
 
P

Paul Rubin

a = 'hello'
a[0] = 'H' # attempt to change first letter to upper case

As CLPython mirrors Python semantics, this results in a TypeError. The
internal representation of an immutable Python string is a mutable Lisp
string, but there is no way you can actually modify it from within CLPython.

How do you manage that? The compiler can't check. Is there some kind
of special dispatch on the assignment statement instead of turning it
into a setf?
 
?

=?ISO-8859-1?Q?Andr=E9_Thieme?=

Paul said:
But there would be even more tokens, the lines going between the nodes
in the trees, for example.

These are painted by the editor.
I also don't count the number of pixels ouf of which a letter consists
as tokens. The nodes in the source tree can be one counting method
which a lot of people could accept.
For Python I also wouldn't count the number of spaces of indentation.


André
--
 

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,432
Messages
2,571,682
Members
48,796
Latest member
Greg L.

Latest Threads

Top