Python syntax in Lisp and Scheme

M

Marcin 'Qrczak' Kowalczyk

Your argument is based on the assumption that whenever people express
_what_ a function does, they do so badly, with an inappropriate name.

Sometimes there is no name which is more clear than the definition.

A random example from OCaml source:
if List.exists (fun q -> q.pat_loc = p.pat_loc) ps2 then ...

Would you write it like this? Please fill the appropriate <name>:
let <name> q = q.pat_loc = p.pat_loc in
if List.exists <name> ps2 then ...

Note that the function must be written here because it uses p which is
local, unless p was moved to its parameters and partially applied here
(this technique works for OCaml because of curring, it won't work for Lisp
or Scheme).
No, it gets shorter, because you don't repeat your use of the same
abstraction over and over.

If it's used only once then the code is longer - see above.
Which is why names should be descriptive.

Great, many long names :)
I think this concept is called variable bindings ;^)

I don't say I don't use variables. I say I don't use variables for *every*
subexpression. And sure I do use named functions, but not every function
deserves to be named.
In the Smalltalk community the rule of thumb is that if a method body
gets to be more than a few lines, you've failed to break it down into
smaller abstractions (i.e., methods).

Hmm, Smalltalk blocks are anonymous functions. Do you reject Smalltalk
blocks too? :)
Yes, but they should live inside the bodies of named functions. Not lie
exposed in the middle of higher level abstractions. Please also see my
reply to Joe Marshall/Prunesquallor a few posts up in this thread.

You contradict yourself. First you say that "any anonymous function syntax
is undesirable", and they you accept anonymous functions in the middle of
higher level abstractions.
 
J

james anderson

Brian McNamara! said:
...

If I understand you correctly, you would insist I write something like

nat :: Parser Int
nat = do {c <- digit; return charToInt c}
`chainl1` combineDigits
where combineDigits = return \x y -> 10*x+y

when i read r.cavallaro's note, i saw no proscriptions. upon rereading it i
observe a value judgement and several characterizations. perhaps the reader
somehow misinterpreted them as pre/proscriptions. pehaps if the term "force"
were replaced with "require" the b.mcnamara would agree that there is no
contraction between them and his post in reply.

....
By only naming the most reusable abstractions (and, ideally, selecting
a set which are mostly orthogonal to one another), we provide a "core
vocabulary" which captures the essential basis of the domain. Lambda
then takes us the rest of the way. In my opinion, a core vocabulary of
named functions plus lambda is better than a separate name for every
abstraction. In natural language, such a scheme would be considered
double-plus-un-good, but in programming, I think it lends itself to the
simplest and most precise specifications.

what is the utility in overstating cavallaro's argument, in order to set up a
straw argument which reaches a conclusion quite in keeping with the original?
perhaps, yes ther is some utility in reiterating that extreme
pre/proscriptions in either direction are counterproductive. but, that in no
way weakens his argument.
 
V

Vijay L

I am in total agreement with Marcin. What you (Raffael) say here
sounds simply like dogma, rather than practical advice for constructing
software.

As a short practical example of what I'm saying, consider code like

[snip]
If I understand you correctly,

It appears you don't.

[snip]
In my opinion, the code here is worse than the original.

I agree.

[big snip]

You're taking things to a huge extreme to prove your point. Obviously
Raffael, being a programmer himself isn't suggesting that each and
every calculation/operation be put in a named function. The best
benchmark, is, I guess, the programmer himself. When programming, if
you find some portion of code that /you/ feel won't be understand when
you're rereading it or, you took a chunk out of a function to debug it
and at that point had to give it a name as another function etc etc;
/then/ keep that portion aside as a named function. It reduces the
burden on the person reading the original function that has been
shortened and, _gives him the choice_ to read this other function that
has been abstracted in that code.

Cheers,
Vijay

All future commitments are optimistic.
 
A

Alexander Schmolck

|> Isn't it true though that the lambda can only contain a single
|> expression and no statements? That seems to limit closures somewhat.

|It limits lambdas. It doesn't limit named functions. Unlike lisp, a
|Python function definition can be nested within a function call, and the
|inner function can access variables in the outer function's closure.

I don't really know Lisp, so I could be wrong. But my understanding is
that CL has a 'let' special form that works fine within a function
definition. In particular, you should be able to define inner functions
by binding a name to a lambda, using 'let'.

Yes David's comment only applies to ancient, non-lexically scoped lisps
(elisp is the only practical example I'm aware of).

'as
 
?

=?iso-8859-1?q?Bj=F6rn_Lindberg?=

|> Isn't it true though that the lambda can only contain a single
|> expression and no statements? That seems to limit closures somewhat.

|It limits lambdas. It doesn't limit named functions. Unlike lisp, a
|Python function definition can be nested within a function call, and the
|inner function can access variables in the outer function's closure.

I don't really know Lisp, so I could be wrong. But my understanding is
that CL has a 'let' special form that works fine within a function
definition. In particular, you should be able to define inner functions
by binding a name to a lambda, using 'let'.

So there's nothing really special about the fact that Python (or
Haskell, ML, etc) can nest function definition. Of course, Haskell's
'let' and 'where' are quite wonderful... even better, syntaxwise, than
Python's nested 'def's.

It is possible (& allowed) to nest defun forms in Lisp, but it does
not achieve the same effect as nesting def:s in python. defun will
name the function in the global environment, so a nested defun will
still define a top-level function. To define a nested function local
in scope to the surrounding function, one of FLET or LABELS is used.


Björn
 
K

ketil+news

james anderson said:
i have no argument with the utility of lambda abstractions. i am
trying only to understand the implications of an argument which, at
least as stated, rather unequivocally deprecates bindings. the
position which was proposed in the forgoing post was rather extreme.

You mean this position?

| A program should balance named and unnamed objects. Both are useful,
| there is a continuum between cases where one or the other is more clear

I have a hard time interpreting this as extremist. Perhaps you should
re-read what you are replying to?

I'm rather baffled that anybody would argue against this, to me too,
it is perfectly natural to use anonymous functions in exression,
whether manifest as a lamda expressions, compositions of functions,
combinators or partial applications (are there more?).

To me, this is the same argument as that against excessive comments,
overly verbose identifiers or annotations (like Hungarian notation) -
if the code is short and clear enough, it only detracts from
readability, and, at worst, becomes misleading or wrong. If the code
isn't clear enough, it should be rewritten.

-kzm
 
J

james anderson

You mean this position?

| A program should balance named and unnamed objects. Both are useful,
| there is a continuum between cases where one or the other is more clear

no i did not mean that position.
I have a hard time interpreting this as extremist. Perhaps you should
re-read what you are replying to?

i did. i also read the post he purported to be replying to. and observed that
there is no need to overstate some elses position in order to, in the end,
make the same point.

the last paragraph, which you site above, stand in strange contrast to the
remainder of the post.
I'm rather baffled that anybody would argue against this,

i'm rather baffled that anybody would think i did. i did not argue against
that last paragraph, but against the rhetoric in the preceeding text.
to me too,
it is perfectly natural to use anonymous functions in exression,
whether manifest as a lamda expressions, compositions of functions,
combinators or partial applications (are there more?).

To me, this is the same argument as that against excessive comments,
overly verbose identifiers or annotations (like Hungarian notation) -
if the code is short and clear enough, it only detracts from
readability, and, at worst, becomes misleading or wrong. If the code
isn't clear enough, it should be rewritten.

....
 
V

Vijay L

Terry Reedy said:
That remembers me that when the languages had significant spaces, the
programming was done with forms, sheets of physical paper preprinted
with empty spaces:
[further idiocy snipped]

I don't know why you categorize it as idiocy. I, having no experience
whatsoever with Python, find it hard to believe that indentation is
easy without "("s and "{"s. (Yes, yes, I know, it can't be so hard.
I've read the other posts, but I find it hard to believe nonetheless.)
In the next paragraph you ask people not to be judgmental and here
you are doing it yourself.
I do believe that several Lispers have suggested that people should
give Lisp a fair trial before rejecting it on account of parentheses
or macros.

I love Lisp primarily /because/ of its macros.
The same goes, of course, for Python and significant
indents/dedents. For most people who try Python, freedom from
visually redundant fences is a feature. Those who find it a bother
after trying are welcome to chose another language.
What makes the comments above doubly absurd is that Lisp has as much
or more need for 'significant spaces' as Python. Compare (1,2,3)
versus (1 2 3). Having the "correct amount of whitespace is *vital*
to the correct operation of a" Lisp program as much as for any other.
Do Lispers therefore use forms? I suspect not ;-)

I don't know what you've meant by "white-space" here.

Removing white-space from (1 2 3) gives me (123). I agree, very
different, but noone knowing the language would consciously do this,
not even a newbie. A newbie, used to other languages, would probably
write (1,2,3) only to get and error saying that the comma cannot be
used outside of a backquote ... and no newbie I know is going to start
off with backquotes. So once he gets an error what does he do? Curse
the language and get on with the same list without the commas. As
time goes by he begins to forget the commas in other languages and
curses /those/ languages. (This happens to me sometimes :)

In Lisp, apart from the whitespace-where-required, whitespace is
insignificant. You can write (1 2


3)
as well as (1 2 3) and get the same meaning.

So we Lispers don't need to use any forms.

Cheers,
Vijay

All future commitments are optimistic.
 
R

Raffael Cavallaro

Marcin 'Qrczak' Kowalczyk said:
You contradict yourself. First you say that "any anonymous function syntax
is undesirable", and they you accept anonymous functions in the middle of
higher level abstractions.

and
Ken Shan said:
So perhaps we should have the programming language outlaw anonymous
functions that are more than 4 lines long...


I thought it was obvious that I was not advocating eliminating anonymous
functions entirely, since I said they should be used inside named
function bodes.

To be completely clear, I'm advocating only using anonymous functions
when two conditions are met:

1. When the functionality provided is _unique_. If that which the
anonymous function is performing is also done elsewhere, especially if
it is done in several different places throughout the code, then that
anonymous function should be recast as a descriptively named function or
macro.

2. If it is necessary to show, at that _particular_ source location,
_how_ a piece of functionality is provided. If the reader doesn't need
to know _how_ things are implemented in that _particular_ source
location, we should use a descriptively named function or macro instead.

I find that many programmers overestimate the "uniqueness" of their
anonymous functions. They tend to repeat the same anonymous function
idiom in many places. Rather than come up with a descriptive name for
_what_ they are doing, they clutter a higher level abstraction with low
level details about _how_ they are doing it. These should be recast as
descriptively named functions or macros.

Many programmers use anonymous functions in places where it is simply
not necessary to know _how_ things are being implemented. In general, we
shouldn't provide the readers of our code with information about its
implementation that isn't necessary to know in the current context.
Doing so obscures the current intent, and sprinkles identical
functionality, which may need to be modified later, in many locations
throughout the program.

To use the example that started this sub-thread, when I'm reading that a
list or a vector has an offset added to each element, I don't need to
know that the add-offset functionality is implemented by means of map.
That piece of information can more safely, and more clearly, reside in a
separate named function, add-offset, which may (or may not) use map to
provide the add-offset functionality. This is especially true, as is
often the case, if add-offset's functionality is used in many locations
throughout the program. We get clearer client code, and easier
maintenance/change if that bit of repeated functionality is isolated in
a single, descriptively named, function or macro. Then, in that
_particular_ source location, (defmethod add-offset...), we _do_ need to
show _how_ add-offset is implemented. So we use anonymous function
syntax there, as needed, in this case, with map and lambda.
 
T

Terry Reedy

That is simply incorrect. It is a small matter to set the readtable
such that things like (+x3) are interpreted as (+ x 3). This would
make it much more difficult to use complicated variable names, but
that's the tradeoff.

So I revise my statement: unless one uses a Lisp with a 'nospace'
option present and turned on, Lisp ...

But why be defensive and nitpicky? I consider this whitespace use (in
the usual case) to be a feature of Lisp. It works well for Lisp
because subexpressions are sublists delimited by (). By contrast,
eliminating commas for Python sequences would be awkward since tuples
are currently *defined* by the present of commas.
There's a bit of a difference between the whitespace separating tokens
and the whitespace that delineates blocks.

We agree; this was one point of my posting. If you are going to slam
Python for its block-delimiting whitespace feature, with or without
having tried it, I would prefer that you say so specificly instead of
using sloppy generalizations like 'whitespace' or 'significance
spaces' that apply to most languages ;-)

As for the programming-form gag, if I were to write substantial
amounts of Python by hand (as I once did with Fortran), I should like
having lined paper with unobtrusive indent ticks every centimeter or
half inch.

Terry J. Reedy
 
C

Christian Szegedy

Raffael said:
2. If it is necessary to show, at that _particular_ source location,
_how_ a piece of functionality is provided. If the reader doesn't need
to know _how_ things are implemented in that _particular_ source
location, we should use a descriptively named function or macro instead.

I find that many programmers overestimate the "uniqueness" of their
anonymous functions. They tend to repeat the same anonymous function
idiom in many places. Rather than come up with a descriptive name for
_what_ they are doing, they clutter a higher level abstraction with low
level details about _how_ they are doing it. These should be recast as
descriptively named functions or macros.

In functional languages, a lot of higher order functions are used for
iterating over some container datastructure. E.g. map,iter,foldl,zip,...

A lambda expression parameter of such functions plays the
same role as a body of a "while" or "for" loop in C.
Do you think that your criterions should be applied to blocks
of codes in C also?
 
J

Jacek Generowicz

HOFs are not a special Lisp thing. Haskell does them much better,
for example... and so does Python.

the alpha and omega of HOFs is that functions are first class
objects that can be passed and returned.

How do you reconcile these two statements ?

[Hint: functions in Lisps are "first class objects that can be passed
and returned"; how does Python (or Haskell) do this "alpha and omega
of HOFs" "much better" ?]
 
J

Jacek Generowicz

David Eppstein said:
To clarify, by "unlike lisp" I meant only that defun doesn't nest

.... and now you'll have to clarify what you mean by "defun doesn't
nest" ...
of course you could use flet, or bind a variable to a lambda, or
whatever.

Yes, you use flet (or labels) if you want a local function definition,
and defun if you want a global one. Lisp caters for both
possibilities. Does Python ?

(And by "cater" I do _not_ mean

def foo(...):
...

globals()['foo'] = foo


Now, if Python had macros ...

)
 
D

David Eppstein

To clarify, by "unlike lisp" I meant only that defun doesn't nest

... and now you'll have to clarify what you mean by "defun doesn't
nest" ...[/QUOTE]

The context of this was in accessing closures from function defs.
Maybe you can call defun from within another function, but it won't give
you a closure.
Yes, you use flet (or labels) if you want a local function definition,
and defun if you want a global one. Lisp caters for both
possibilities. Does Python ?

Yes.

def outer():
x = 3
global inner
def inner():
print x

outer()
inner()

....prints 3, as expected.
 
A

Alexander Schmolck

Consider this python code (lines numbered for exposition):

Thanks for going through the trouble of posting an elaborate example.
1 def dump(st):
2 mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime = st
3 print "- size:", size, "bytes"
4 print "- owner:", uid, gid
5 print "- created:", time.ctime(ctime)
6 print "- last accessed:", time.ctime(atime)
7 print "- last modified:", time.ctime(mtime)
8 print "- mode:", oct(mode)
9 print "- inode/dev:", ino, dev
10
11 def index(directory):
12 # like os.listdir, but traverses directory trees
13 stack = [directory]
14 files = []
15 while stack:
16 directory = stack.pop()
17 for file in os.listdir(directory):
18 fullname = os.path.join(directory, file)
19 files.append(fullname)
20 if os.path.isdir(fullname) and not os.path.islink(fullname):
21 stack.append(fullname)
22 return files
[...]

But let us consider cutting lines 6 and 7 and putting them
between lines 21 and 22. We get this:

15 while stack:
16 directory = stack.pop()
17 for file in os.listdir(directory):
18 fullname = os.path.join(directory, file)
19 files.append(fullname)
20 if os.path.isdir(fullname) and not os.path.islink(fullname):
21 stack.append(fullname)
6 print "- last accessed:", time.ctime(atime)
7 print "- last modified:", time.ctime(mtime)
22 return files

But it is unclear whether the intent was to be outside the while,
or outside the for, or part of the if.

I don't think so. Before pasting you just move your cursor to #1 #2 or #3 to
achieve the respective results:
15 while stack:
16 directory = stack.pop()
17 for file in os.listdir(directory):
18 fullname = os.path.join(directory, file)
19 files.append(fullname)
20 if os.path.isdir(fullname) and not os.path.islink(fullname):
21 stack.append(fullname) #1 #2 #3
22 return files

What's wrong with that? (If nothing, I'll finally hack it up in elisp,
unless someone already has done it).

[snipped]
Now consider this `pseudo-equivalent' parenthesized code:

1 (def dump (st)
2 (destructuring-bind (mode ino dev nlink uid gid size atime mtime ctime) st
3 (print "- size:" size "bytes")
4 (print "- owner:" uid gid)
5 (print "- created:" (time.ctime ctime))
6 (print "- last accessed:" (time.ctime atime))
7 (print "- last modified:" (time.ctime mtime))
8 (print "- mode:" (oct mode))
9 (print "- inode/dev:" ino dev)))
10
11 (def index (directory)
12 ;; like os.listdir, but traverses directory trees
13 (let ((stack directory)
14 (files '()))
15 (while stack
16 (setq directory (stack-pop))
17 (dolist (file (os-listdir directory))
18 (let ((fullname (os-path-join directory file)))
19 (push fullname files)
20 (if (and (os-path-isdir fullname) (not (os-path-islink fullname)))
21 (push fullname stack)))))
22 files))

If we cut lines 6 and 7 with the intent of inserting them
in the vicinity of line 21, we have several options (as in python),
but rather than insert them incorrectly and then fix them, we have
the option of inserting them into the correct place to begin with.
In the line `(push fullname stack)))))', there are several close
parens that indicate the closing of the WHILE, DOLIST, LET, and IF,
assuming we wanted to include the lines in the DOLIST, but not
in the LET or IF, we'd insert here:
V
21 (push fullname stack))) ))

AFAICT this is a more difficult editing operation to get right than what I
suggested above to get the desired effect for python and you still have to
reindent.
The resulting code is ugly: [...]
But it is correct.

Correct as in "has the desired behavior" is not the only thing that counts.
(Incidentally inserting at that point is easy: you move the cursor over
the parens until the matching one at the beginning of the DOLIST begins
to blink. At this point, you know that you are at the same syntactic level
as the dolist.)

Sure, it's not very taxing, but I think still slightly more so than placing
the cursor at #1 #2 or #3 for the python example (you need to invoke extra
functionality, even if this functionality is quite convinient and you need to
devote visual attention to an extra site).
Let me expand on this point. The lines I cut are very similar to each
other, and very different from the lines where I placed them. But
suppose they were not, and I had ended up with this:

19 files.append(fullname)
20 if os.path.isdir(fullname) and not os.path.islink(fullname):
21 stack.append(fullname)
6 print "- last accessed:", time.ctime(atime)
7 print "- last modified:", time.ctime(mtime)
22 print "- copacetic"
23 return files

Now you can see that lines 6 and 7 ought to be re-indented, but line 22
should not. It would be rather easy to either accidentally group line seven
with line 22, or conversely line 22 with line 7.

True, but as I stated above AFAICT there is no need to paste-and fix, you can
just paste correctly (Digression for the emacs-interested: a really
emacs-savvy user will paste and fix in a pretty safe fashion anyway, because
the source of error above would be to inadvertently select a (wrong) region
for reindentation. If you just reindent (with C-c> or C-c<) right after
pasting (the pasted code will automatically form the region then) you don't
have this problem).
Forgetting to indent properly in a lisp program does not yield
erroneous code.

I think this highlights an implicit but mistaken assumption underlying
anti WS arguments.

No, of course forgetting to indent does not *directly* yield erroneous code.
That's not good enough, however because it is associated with 2 problems:

1) masking an error: if you perform an operation that can go wrong and
frequently does (such as issuing an editing command) then enforcing manual
verification of the desired outcome (read C-M-\) is a source of error.

2) misleading later readers (including the programmer himself, esp. while he's
editing): the bad indentation might well suggest an altnernative meaning
from the actually intended one that goes unnoticed till someone reindents
the code -- again a source of errors.

This is correct. But what is recommended here is to use a simple tool to
enhance readability and do a trivial syntactic check.

I think "enhance readability" is more than an understatement. Lisp code of
reasonable complexity is simply unreadable if not or arbitrarily indented
(even more so than XML!).
Would that this were the case. Lisp code that is poorly indented will still
run.
Python code that is poorly indented will not. I have seen people write lisp
code like this:

(defun factorial (x)
(if (> x 0)
x
(*
(factorial (- x 1))
x
)))

I still tell them to re-indent it. A beginner writing python in this manner
would be unable to make the code run.

But the fact that you can't do this in python (and BTW I've never seen a
*python* newbie TRY -- have you?) is a FEATURE, for crying out loud.

Or what exactly do you think the benefit of making it easier for beginners to
write error-prone and unreadable code to be? Why is it a paedagogical
disadvantageous for python beginners to automatically write readable code that
does what they think it does (at least as far as block structure is concerned)
compared to lisp newbies who either often write code that doesn't do what they
(and other readers) think it does (because of indentation/paren mismatch) or
that is inpenetrable (just think about how many hours these people have wasted
trying to decipher and debug their code before you gave them the fatherly
advice to try to indent it)?
Ok. For any sort of semantic error (one in which a statement is
associated with an incorrect group) one could make in python, there is
an analagous one in lisp, and vice versa. This is simply because both
have unambiguous parse trees.

However, there is a class of *syntactic* error that is possible in
python, but is not possible in lisp (or C or any language with
balanced delimiters). Moreover, this class of error is common,
frequently encountered during editing, and it cannot be detected
mechanically.

This argument basically boils down to "lisp is more redundant and therefore
less error prone". This inself is not a valid argument as it depends on the
type of redundancy and the extend to which this redundancy itself causes
errors, e.g. by rendering the code more obscure by additional verbosity (as in
xml compared to sexps); whether this redundancy helps or hinders perception
and how this redundancy interferes with the editing process.

As an example of such interference in the case of lisp consider the example of
commenting/deleteing/appending after a line with surplus trailing parens
(corresponding ot opening parens in earlier lines). Conceptually this line is
the same as all the other lines in the code in the same block, but you have to
use quite different (and more complicated and error-prone) commands to achieve
the same editing process. Not so in python.
Consider this thought experiment: pick a character (like parenthesis
for example) go to a random line in a lisp file and insert four of them.

This thougt experiment seems of limited informational value. While I can see
how you might accidentally indent 4 spaces by pressing tab (which you'd
normally notice) accidently, inserting 4 spaces or 4 parens (by pressing space
or ')') seems highly unlikely to me.

Additionally most of the time the code is actively being edited and thus *not*
in a consistent state -- this is how most editing errors occur and I don't
think lisp compares favourably to python here.

This is largely due to the fact that the relationship between suggested
meaning and actual meaning of the code is automatically maintained in python
and only semi-automatically by emacs/lisp. As an example consider introducing
additional local vars by adding a let-block. This is not quite equivalent to
the same example in python (which is trivial, editing wise, baring potential
for local name-conflicts), but close enough and (unless you've plenty at
routine at editing lisp code) it's pretty easy to screw things up here (paren
matching makes it easy to match with visually exposed outer parents (viz.
corresponding to e.g. "(LET"'), but I find it's still pretty easy to end up
with wrong parens inside, viz. the var forms/body) -- the more code you write
before reindenting everything the higher the likelyhood something went wrong,
although the total number of parens will be correct.

Anyway, back to your thought-experiment:
Is the result syntactically correct? No. Could a naive user find them?
Trivially. Could I program Emacs to find them? Sure.

Now go to a random line in a python file and insert four spaces. Is

Uhm where? A) Anywhere in the line? Since only *leading whitespace* is
significant this wouldn't alter the meaning (safe in strings and identifiers
-- as in CL). So I assume you mean B) at the beginning of the line, in which
case:
the result syntactically correct?
Likely.

Depends on what you mean by "likely". A necessary condition for the result to
be syntactically valid and semantically different is that the preceding
(non-comment) line has the same indent (which occurs in about of 10% of the
lines in a representative sample [1]).
Could a naive user find them?
Unlikely.

Uhm, actually extremely likely because 90% of the cases in B) yield invalid
syntax, which both python and your editor can easily figure out for the naive
user without even running the code.
Could you write a program to find them? No.

Sure I could, but writing one that performs better then say 95% would involve
an amount of work unwarranted by the unrealistic scenario.
> Delete four adjacent parens in a Lisp file. Will it still compile? No.
Will it even be parsable? No.

Delete four adjacent spaces in a Python file. Will it still compile?
Likely.

See above.
^^^^^^^
[thinko; should have been Gricean]
I thought that whitespace was significant to Python.

Only human readable whitespace [2].
My computer does not display whitespace.

Mine sure does.

if bar:
foo

and

if bar:
foo

look quite different on my computer. Since this (leading ws) is the only type
of whitespace that semtantically matters to python, I still fail to see your
point.

I understand that most computers do
not. There are few fonts that have glyphs at the space character.

Since having the correct amount of whitespace is *vital* to the
correct operation of a Python program, it seems that the task of
maintaining it is made that munch more difficult because it is only
conspicuous by its absence.

No idea what you're driving at -- maybe you've got some wrong assumptions
about the details of python's syntax?
Sussman is careful to separate the equations of classical mechanics
from the *implementation* of those equations in the computer, the

I really don't want to push this point too hard, but why not use sexps for
both if sexp-readability was near optimal (Iverson seems to have done the
equivlanent for his mathematical books using APL/J)?
former are written using a functional mathematical notation similar to
that used by Spivak, the latter in Scheme. The two appendixes give
the details. Sussman, however, notes ``For very complicated
expressions the prefix notation of Scheme is often better''

That statement seems to suggest to me that in the general case MN is
preferable.
And where did I claim that? You originally stated:

Well I didn't explicitly say you did, but you contested the claim requoted
below about ')))))))))' (I'm still not quite sure why) and stated that Abelson
and Sussman would disagree with my assesments of parens and whitespace, so
this interpretation seemed not entirely implausible to me.
Still, I'm sure you're familiar with the following quote (with which I most
heartily agree):

"[P]rograms must be written for people to read, and only incidentally for
machines to execute."

People can't "read" '))))))))'.

Quoting Sussman and Abelson as a prelude to stating that parenthesis are
unreadable is hardly going to be convincing to anyone.

I didn't say parens are generally unreadable, as the quote above shows I said
that people can't read *large numbers of trailing parens*, which is quite
different (I would also have had to contradict myself otherwise in stating
that properly formated lisp reads well)?

The reason why I brought up this quote is because you were trashing python's
indentation based syntax (which, remember, I didn't claim was suitable or
"better" than sexps for lisp or similar expression based languages) as an
illustration that python's syntax at least compares favourably to sexps in an
area that is of regarded as very important by prominent members of the
lisp/scheme community.

Look, I *like* lisp syntax (and I'm even happy with you to claim it's better
than python's, as long as I can dissuade you from claiming that it's terribly
error-prone, unless you've actually tried writing some reasonable amount of
python yourself).

But clearly a syntax were some vital information is not so much there for
people to read but for editors and compilers is not in full conformance with
the above goal (if you really disagree with this, again why alias '[]' in
pretty much all schemes if nested '()'s are just as readable?).

In python you don't need the editor to semi-automatically put comments in your
code (which is what pressing 'M-C-\' amounts to) to render it intelligible.
Does it matter?

Of course it does matter. We were talking about readability by *humans*.

I know that emacs has no trouble "reading" 7 trailing parenthesis (i.e.
extracting the relevant semantic information from them, namely what they
delimit), but I doubt you (or other humans) can (without some prosthetic aid
in the form of brain implants or manually invoked emacs commands).

You are presupposing *two* errors of two different kinds here: the
accidental inclusion of an extra parenthesis after bar *and* the
accidental omission of a parenthesis after watz.

The kind of error I am talking about with Python code is a single
error of either omission or inclusion.

In my book this is a *single* error of one kind: accidental misplacement of a
parenthesis (sure happens to me as an *atomic* operation). Does this never
happen to you (maybe you use more effective editing strategies, M-( etc.)?

I could hardly care less.

Well, anyway I'll leave it at that before this one completely degrades, too. I
hope at least the discussion of editing errors and strategies will have had
some value.

'as

Footnotes

[1] [I really get to write more than my fair share of perl in this thread ;|]

perl -ne '/^ */; $lines++; $canIndent++ if (length($last)
== length($&)+4);$last = $&; END{print "LOC:$lines CANINDENT:$canIndent\n";
print "ratio:" . $canIndent/$lines . "\n"}' /usr/local/lib/python2.3/*.py

LOC:76264 CANINDENT:9307
ratio:0.122036609671667

This is an upper boundary, BTW. Not all these lines would semantically
change if reindented.

[2] modulo #\Tab which we can include from the current discussion because its
use is discouraged and the wart that python still allows use of tabs is not
really relevant to evaluating the merrits of the use of identation for
block-structure indication as such.
 
J

Jock Cooper

David Eppstein said:
It limits lambdas. It doesn't limit named functions. Unlike lisp, a
Python function definition can be nested within a function call, and the
inner function can access variables in the outer function's closure.

To clarify, by "unlike lisp" I meant only that defun doesn't nest (at
least in the lisps I've programmed) -- of course you could use flet, or
bind a variable to a lambda, or whatever.
[/QUOTE]

Ok so in Python a function can DEF another function in its body. I assume
this can be returned to the caller. When you have a nested DEF like that,
is the nested function's name globally visible?
 
A

Alexander Schmolck

Peter Seibel said:
Yes. But that's no different with macros than if someone decided that
they like BEGIN and END better than FIRST and REST (or CAR/CDR) and so wrote:

(defun begin (list) (first list))
(defun end (list) (rest list))

The reason it's different is that people apparently engage in the former (we
seem to agree undesirable) behavior, but not in the latter. I suppose this is
because programmers generally feel that wasting CPU cycles is a bad thing (I
know you could DECLAIM INLINE the above in CL). Also, you can't do things like
AIF as a function.
As almost everyone who has stuck up for Lisp-style macros has
said--they are just another way of creating abstractions and thus, by
necessity, allow for the possibility of people creating bad
abstractions.

I guess so much everyone agrees on:)

The point that the lisp side seems to under-appreciate is that (even powerful)
abstractions have their limitations and also come at a cost. This cost should
be carefully analyzed because it is not always worth paying (and even if it
is, as is surely the case for macros in CL, awareness of the downside might
help minimize it).

Unfortunately, I'm pretty worn out by this thread now and have to catch up
with my work, so I won't go into detail.
But if I come up with examples of bad functional abstractions or poorly
designed classes, are you going to abandon functions and classes? Probably
not.

Depends on the *implementation* of those constructs themselves and *which*
features (e.g. multiple inheritance). I'd forgo C++ for some saner language
without built-in OO anytime.
It really is the same thing.

Would you like to see continuations in CL? If not, note that your argument
would be equally applicable.

Well, I admire your faith in Python. ;-)

It would be less a question of faith, but more an incorrect conclusion.
No, you're mistaken. In my test framework, test results are signaled
with "conditions" which are the Common Lisp version of exceptions. Run
in interactive mode, I will be dropped into the debugger at the point
the test case fails where I can use all the facilities of the debugger

Yep, I'm aware.
to figure out what went wrong including jumping to the code in
question

OK, I was under the maybe mistaken impression that this wasn't generally
possible (if by "jumping to the code" you mean actually jumping to *the line*
where the error occured, in your editor window).
examining stack framse, and then if I think I've figured out
the problem, I can redefine a function or two and retry the test case
and proceed with the rest of my test run with the fixed code.

Yes, restarts sure are handy for interactive development (and absent in
python).
(Obviously, after such a run you'd want to re-run the earlier tests to
make sure you hadn't regressed. If I really wanted, I could keep track
of the tests that had been run prior to such a change and offer to
rerun them automatically.)


Yup. Me too. Can you retry the test case and proceed with the rest of
your tests?

Nope, not to my knowledge (if anyone knows how to do this with pdb, I'd love
to hear about it).
Yup. That's really handy. I agree.

So, in all sincere curiosity, why did you assume that this couldn't be
done in Lisp.

Well, basically because I tried to get some reasonable debugging environment
working (with ilisp/cmucl or clisp) and failed miserably (and googling didn't
help much further either). That macros (esp. reader macros) wouldn't interact
all that well with source level debugging also didn't seem like the most
unreasonable assumption.

Any suggestions how to get something like the following to source debug with
emacs/ilisp/cmucl?

;;test-debug.lisp
(declaim (optimize (debug 3)))
(defun buggy-defun ()
(loop with bar = 10
for i below 30
collect (/ i (- i bar)))) ;<= I'd like to start stepping here in emacs
(buggy-defun)

I really am interested as I'm writing a book about Common Lisp and part of
the challenge is dealing with people's existing ideas about the language.

Good idea. I think it would also be worthwhile to have a good luck at close
competitors like python in order to do so effectively.
Feel free to email me directly if you consider that too far offtopic for
c.l.python.

-Peter

'as
 
J

Joe Marshall

Additionally most of the time the code is actively being edited and thus *not*
in a consistent state -- this is how most editing errors occur and I don't
think lisp compares favourably to python here.

EXACTLY! The issue is that the inconsistent state in lisp is rarely
a legal expression, but an inconsistent state in Python often is.

I'll answer the rest of the post a bit later. I had an idea I want
to think about.
 
M

MetalOne

Raffael Cavallaro

I don't know why but I feel like trying to summarize.

I initially thought your position was that lambdas should never be
used. I believe that Brian McNamara and Ken Shan presented powerful
arguments in support of lambda. Your position now appears to have
changed to state that lambdas are ok to use, but their use should be
restricted. One point would appear to desire avoiding duplicate
lambdas. This makes sense. Duplication of this sort is often found
in "if statment" conditional tests also. The next point would be to
name the function if a good name can be found. I believe that
sometimes the code is clearer than a name. Mathematical notation was
invented because natural language is imprecise. Sometimes a name is
better than the code. The name gives a good idea of the "how" and
perhaps you can defer looking at the "how". Sometimes I think using
code in combination with a comment is better. A comment can say a
little more than a name, and the code gives the precision. So as
Marcin said, it is a balancing act to create readable code.

I would like to say that I have found this entire thread very
comforting. I have been programming for 18 years now. For the most
part, when I read other peoples code I see nothing but 300+ line
functions. I have come to feel like most programmer's have no idea
what they are doing. But when you're writing small functions and
everybody else is writing 300+ line functions you begin to wonder if
it is you that is doing something wrong. It is nice to see that other
people actually do think about how to write and structure good code.
 

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

Forum statistics

Threads
473,787
Messages
2,569,629
Members
45,329
Latest member
InezZ76898

Latest Threads

Top