A critic of Guido's blog on Python's lambda

J

Joe Marshall

Alex said:
Absolutely. That's why firms who are interested in building *seriously*
large scale systems, like my employer (and supplier of your free mail
account), would never, EVER use Python,

So how much Python code runs when I check my gmail?
nor employ in prominent
positions such people as the language's inventor and BDFL, the author of
the most used checking tool for it, and the author of the best-selling
reference book about that language; and, for that matter, a Director of
Search Quality who, while personally a world-renowned expert of AI and
LISP, is on record as supporting Python very strongly, and publically
stating its importance to said employer.

Doesn't Google also employ such people as the inventor of Limbo
programming language, one of the inventors of Dylan, and a Smalltalk
expert?
 
J

Joe Marshall

Alex said:
Your "pragmatic benefits", if such they were, would also apply to the
issue of "magic numbers", which was discussed in another subthread of
this unending thread; are you therefore arguing, contrary to widespread
opinion [also concurred in by an apparently-Lisp-oriented discussant],
that it's BETTER to have magic unexplained numbers appear as numeric
constants "out of nowhere" smack in the middle of expressions, rather
than get NAMED separately and then have the names be used? If you
really believe in the importance of the "pragmatic benefits" you claim,
then to be consistent you should be arguing that...:

return total_amount * 1.19

is vastly superior to the alternative which most everybody would deem
preferable,

VAT_MULTIPLIER = 1.19
return total_amount * VAT_MULTIPLIER

because the alternative with the magic number splattered inexplicably
smack in the middle of code "communicated the fact" that it's used only
within that expression, and makes all context available without having
to look "elsewhere" (just one statement up of course, but then this
would be identically so if the "one statement up" was a def, and we were
discussing named vs unnamed functions vs "magic numbers").

Most languages allow `unnamed numbers'. The `VAT_MULTIPLIER' argument
is a
strawman. Would you want to have to use a special syntax to name the
increment
in loop?

defnumber zero 0
defnumber one { successor (zero); }

for (int i = zero; i < limit; i += one) { ...}

If you language allows unnamed integers, unnamed strings, unnamed
characters, unnamed arrays or aggregates, unnamed floats, unnamed
expressions, unnamed statements, unnamed argument lists, etc. why
*require* a name for trivial functions?
Wouldn't all the other constructs benefit by having a required name as
well?
To my view of thinking, offering multiple semantically equivalent ways
(or, perhaps worse, "nearly equivalent but with subtle differences"
ones) to perform identical tasks is a *HUGE* conceptual cost: I like
languages that are and stay SMALL and SIMPLE.

Then why not stick with S and K combinators? There are few languages
SMALLER and
SIMPLER.
 
R

Raffael Cavallaro

The phrase "only one obvious way..." is nearly the most absurd
marketing bullshit I have ever heard; topped only by "it fits your
brain". Why are so many clearly intelligent and apparently
self-respecting hard-core software engineers repeating this kind of
claptrap?


Really should read "only one obvious way to people with a similar
background and little creativity" or "it fits your brain if you've
mostly programmed in algol syntax languages and alternative ideas make
said brain hurt."

trimmed to c.l.python and c.l.lisp
 
K

Ken Tilton

[Sorry, i was just reading comp.lang.lisp, missed the following till
someone mentioned it in email. k]

Alex said:
You're right that the above-quoted snipped does sound exactly like
Python's properties, but I suspect that's partly because it's a very
concise summary. A property, as such, recomputes the value each and
every time, whether the computation is necessary or not; in other words,
it performs no automatic caching/memoizing.

Right, and the first thing we did was simply that, no memoizing. The
second thing we did was memoize without tracking dependencies, updating
everything on each pass thru the eventloop. We knew both would not
(uh-oh) scale, but we wanted to see if the approach solved the
computational problem that started the whole research programme.

Soon enough we were tracking dependencies.

A more interesting project might therefore be a custom descriptor, one
that's property-like but also deals with "caching wherever that's
possible". This adds interesting layers of complexity, some of them not
too hard (auto-detecting dependencies by introspection), others really
challenging (reliably determining what attributes have changed since
last recomputation of a property). Intuition tells me that the latter
problem is equivalent to the Halting Problem -- if somewhere I "see" a
call to self.foo.zap(), even if I can reliably determine the leafmost
type of self.foo, I'm still left with the issue of analyzing the code
for method zap to find out if it changes self.foo on this occasion, or
not -- there being no constraint on that code, this may be too hard.

"no constraint on the code" is a sufficient objection but also: if the
rule for the code is (in neutral pseudo-code)

if a is true
then return b
else return c

...you only want to depend on one of b or c at a time. (also, not do the
internal dependency tracking on, say, b if it happens to hold an
immutable value (part of the Cells API). Nice performance win, it turns out.

I just keep what I call a "datapulse ID", sequentially growing from
zero, in a global variable. Each ruled Cell keeps track of its memoized
value, datapulse stamp, and whether it in fact changed value in reaching
its current datapulse stamp. (I can reach the current datapulse stamp by
determining no dependency (direct or indirect recursively down the
dependency graph) is both more current than me /and/ in fact changed in
value getting there.[1] If not, I take on the current datapulse but
never run my rule. Or, if yes, I run my rule but might compute the same
value as last time. Either way, I can flag myself as current but
not-actually-changed.)

So we have push and pull. The whole thing gets kicked off by a setting
operation on some slot, who notifies dependents that they should
recompute. This is a cascade of further notifications if anyone notified
recomputes and in fact computes a different value. The pull comes in
while rules are running to make sure no obsolete value gets used. So the
dependency graph gets updated JIT during rule evaluations, possively
recursively so.

The practical problem of detecting alterations may be softened by
realizing that some false positives are probably OK -- if I know that
self.foo.zap() *MAY* alter self.foo, I might make my life simpler by
assuming that it *HAS* altered it. This will cause some recomputations
of property-like descriptors' values that might theoretically have been
avoided, "ah well", not a killer issue. Perhaps a more constructive
approach would be: start by assuming the pseudoproperty always
recomputes, like a real property would; then move toward avoiding SOME
needless recomputations when you can PROVE they're needless. You'll
never avoid ALL needless recomputations, but if you avoid enough of them
to pay for the needed introspection and analysis, it may still be a win.
As to whether it's enough of a real-world win to warrant the project, I
pass -- in a research setting it would surely be a worthwhile study, in
a production setting there are other optimizations that look like
lower-hanging fruits to me. But, I'm sure the Cells people will be back
with further illustrations of the power of their approach, beyond mere
"properties with _some_ automatic-caching abilities".

Who knows, maybe something like that lies in the future of cells. It
would not be because of the need for update being undecidable, it would
be because the decision somehow would be too expensive compared to "Just
Run the Rule!"[2] It seems every new application brings up interesting
new requirements where Cells can usefully be extended with new
capabilities. But over time the code has gotten simpler and simpler, so
I think we are headed in the right direction.

kenny

[1] Aha! I see a flaw. Arises if two datapulses pass before I (a cell
<g>) get read and must determine if my cache is obsolete, and some
dependency changed in the first datapulse but not the second. I have to
enhance regression test suite and cure. One possible cure is akin to
your thinking: if I missed a generation, I have to assume it changed in
the missed generation. The alternatives are keeping a history of my
datapulses going back no further than the last true change, or having a
Cell keep a separate record of the last value used from each dependency.
The second could get expensive if I consult a lot of other cells,
while the first ... ah wait, I do not need a history, I just need one
new attribute: datapulse-of-latest-actual change. Sweet.

[2] I am making a mental note to look into that optimization.
 
C

Cameron Laird

.
.
.
Of course, the choice of Python does mean that, when we really truly
need a "domain specific little language", we have to implement it as a
language in its own right, rather than piggybacking it on top of a
general-purpose language as Lisp would no doubt afford; see
<http://labs.google.com/papers/sawzall.html> for such a DSLL developed
at Google. However, I think this tradeoff is worthwhile, and, in
particular, does not impede scaling.


Alex

You lost me, Alex.

I recognize that most of this thread has been far away, in the
land of the anonymity of function definitions, and so on. I've
redirected follow-ups to clp.

On this one isolated matter, though, I'm confused, Alex: I sure
think *I* have been writing DSLs as specializations of Python,
and NOT as "a language in its own right". Have I been fooling
myself, or are you making the point that Lisp-based DSLs live in
a larger syntactic universe than Python's, or ...?
 
K

Ken Tilton

Ken said:
I just keep what I call a "datapulse ID", sequentially growing from
zero, in a global variable. Each ruled Cell keeps track of its memoized
value, datapulse stamp, and whether it in fact changed value in reaching
its current datapulse stamp. (I can reach the current datapulse stamp by
determining no dependency (direct or indirect recursively down the
dependency graph) is both more current than me /and/ in fact changed in
value getting there.[1] If not, I take on the current datapulse but
never run my rule. Or, if yes, I run my rule but might compute the same
value as last time. Either way, I can flag myself as current but
not-actually-changed.)
.....


[1] Aha! I see a flaw. Arises if two datapulses pass before I (a cell
<g>) get read and must determine if my cache is obsolete, and some
dependency changed in the first datapulse but not the second.

No, I do not think that can happen. I was conflating two mutually
exclusive paths. If I am checking a dependency that means it would have
notified me when it in fact changed. If my rule is running i will always
get a valid value from a read. So i do not think there is a hole in
there anywhere.

In any case, I would not have needed a new last-changed-datapulse slot,
i could just change the "changed" flag to be last-changed-datapulse.

kenny

--
Cells: http://common-lisp.net/project/cells/

"Have you ever been in a relationship?"
Attorney for Mary Winkler, confessed killer of her
minister husband, when asked if the couple had
marital problems.
 
T

Thomas F. Burdick

Ken Tilton said:
No, you do not want on-change handlers propagating data to other
slots, though that is a sound albeit primitive way of improving
self-consistency of data in big apps. The productivity win with
VisiCalc was that one simply writes rules that use other cells, and
the system keeps track of what to update as any cell changes for
you. You have that exactly backwards: every slot has to know what
other slots to update. Ick.

No no, that's fine and the way it should be: when you change a slot,
it should know who to update. And that's also the way it works in
Cells. The trick is that Cells takes care of that part for you: all
the *programmer* has to care about is what values a slot depends on --
Cells takes care of inverting that for you, which is important because
that's a job that a computer is much better at than a human.
 
P

Patrick May

...an alleged reply to me, which in fact quotes (and responds to)
only to statements by Brian, without mentioning Brian...

Mr May, it seems that you're badly confused regarding Usenet's
quoting conventions.

It seems that someone pisses in your cornflakes nearly every
morning.

For the record, I was attempting to respond to your post which I
only saw quoted in another message. Please excuse any accidental
misquoting.
You still need to look a little bit upwards to the "point of use",
almost invariably, to see what's bound to which names -- so, you DO
"have to look elsewhere", nullifying this alleged benefit -- looking at
the def statement, immediately before the "point of use", is really no
pragmatic cost when you have to go further up to get the context for all
other names used (are they arguments of this function, variables from a
lexically-containing outer function, assigned somewhere...), which is
almost always.

It appears that you write much longer functions than I generally
do. Requiring that all functions be named adds even more to the
clutter.
And if you think it's an important pragmatic advantage to limit
"potential scope" drastically, nothing stops you from wrapping
functions just for that purpose around your intended scope

Or, I could just use a language that supports unnamed functions.
Your "pragmatic benefits", if such they were, would also apply to the
issue of "magic numbers",

That claim is, frankly, silly. A function is far more
understandable without a name than a value like 1.19 in isolation.
The situations aren't remotely comparable.
To my view of thinking, offering multiple semantically equivalent
ways (or, perhaps worse, "nearly equivalent but with subtle
differences" ones) to perform identical tasks is a *HUGE* conceptual
cost: I like languages that are and stay SMALL and SIMPLE.

Like Scheme?

Regards,

Patrick
 
K

Ken Tilton

Thomas said:
No no, that's fine and the way it should be: when you change a slot,
it should know who to update. And that's also the way it works in
Cells. The trick is that Cells takes care of that part for you: all
the *programmer* has to care about is what values a slot depends on --
Cells takes care of inverting that for you, which is important because
that's a job that a computer is much better at than a human.

Well, as long as we are being precise, an important distinction is being
obscured here: I was objecting to a slot "knowing" who to update thanks
to having been hardcoded to update certain other slots. When you say
"Cells takes care of that", it is important to note that it does so
dynamically at runtime based on actual usage of one slot by the rule for
another slot.

kt

--
Cells: http://common-lisp.net/project/cells/

"Have you ever been in a relationship?"
Attorney for Mary Winkler, confessed killer of her
minister husband, when asked if the couple had
marital problems.
 
D

David C. Ullrich

No no, that's fine and the way it should be: when you change a slot,
it should know who to update. And that's also the way it works in
Cells. The trick is that Cells takes care of that part for you:

I'm glad you said that - this may be what he meant, but it seems
more plausible than what he actually said.
all
the *programmer* has to care about is what values a slot depends on --
Cells takes care of inverting that for you, which is important because
that's a job that a computer is much better at than a human.

Fine. I suppose that is better; if b is going to return a + 1
the fact that this is what b returns should belong to b, not
to a. So a has an update list including b, so when a's value
is set a tells b it needs to update itself.

If we're allowed to pass (at some point, to some constructor
or other) something like (b, a + 1, [a]), which sets up a
cell b that shall return a + 1, and where the [a] is used
in the constructor to tell a to add b to a's update list
then this seems like no big deal.

And doing that doesn't seem so bad - now when the programmer
is writing b he has to decide that it should return a + 1
and also explicitly state that b shall depend on a; this
is all nice and localized, it's still _b_ telling _a_ to
add b to a's update list, and the programmer only has
to figure out what _b_ depends on when he's writing _b_.
Doesn't seem so bad.

But of course it would be better to be able to pass just
something morally equivalent to (b, a + 1) to whatever
constructor and have the system figure out automatically
that since b returns a + 1 it has to add a to b's update
list. There must be some simple trick to accomplish that
(using Python, without parsing code). (I begin to see the
point to the comment about how the callbacks should fire
when things are constucted.) Exactly what the trick is I
don't see immediately.

In Cells do we just pass a rule using other cells to
determine this cell's value, or do we also include
an explicit list of cells that this cell depends on?

************************

David C. Ullrich
 
K

Kaz Kylheku

Steve said:
Pretty much correct. The complete thought was that it would be painful
all out of proportion to the benefit.

See, you don't need multi-line lambda, because you can do this:


def make_adder(x):
def adder_func(y):
sum = x + y
return sum
return adder_func

Now imagine you had to do this with every object.

def add_five(x)
# return x + 5 <-- anonymous integer literal, not allowed!!!
five = 5 # define it first
return x + five

Think about the ramifications of every object having to have a name in
some environment, so that at the leaves of all expressions, only names
appear, and literals can only be used in definitions of names.

Also, what happens in the caller who invokes make_adder? Something like
this:

adder = make_adder(42)

Or perhaps even something like this

make_adder(2)(3) --> 5

Look, here the function has no name. Why is that allowed? If anonymous
functions are undesireable, shouldn't there be a requirement that the
result of make_adder has to be bound to a name, and then the name must
be used?
Note that make_adder() doesn't use lambda, and yet it makes a custom
function with more than one line. Indented, even.

That function is not exactly custom. What is custom are the environment
bindings that it captures. The code body comes from the program itself.

What about actually creating the source code of a function at run-time
and compiling it?

(let ((source-code (list 'lambda (list 'x 'y) ...)))
(compile nil source-code))

Here, we are applying the compiler (available at run-time) to syntax
which represents a function. The compiler analyzes the syntax and
compiles the function for us, giving us an object that can be called.

Without that syntax which can represent a function, what do you pass to
the compiler?

If we didn't have lambda in Lisp, we could still take advantage of the
fact that the compiler can also take an interpreted function object and
compile that, rather than source code. So we could put together an
expression which looks like this:

(flet ((some-name (x y) ...)) #'some-name)

We could EVAL this expression, which would give us a function object,
which can then be passed to COMPILE. So we have to involve the
evaluator in addition to the compiler, and it only works because the
compiler is flexible enough to accept function objects in addition to
source code.
No; lambda is a bit more convenient. But this doesn't seem like a very
big issue worth a flame war. If GvR says multi-line lambda would make
the lexer more complicated and he doesn't think it's worth all the effort,
I don't see any need to argue about it.

I.e. GvR is the supreme authority. If GvR rationalizes something as
being good for himself, that's good enough for me and everyone else.
I won't say more, since Alex Martelli already pointed out that Google is
doing big things with Python and it seems to scale well for them.

That's pretty amazing for something that doesn't even have a native
compiler, and big mutexes in its intepreter core.

Look at "docs.python.org" in section 8.1 en titled "Thread State and
the Global Interpreter Lock":

"The Python interpreter is not fully thread safe. In order to support
multi-threaded Python programs, there's a global lock that must be held
by the current thread before it can safely access Python objects.
Without the lock, even the simplest operations could cause problems in
a multi-threaded program: for example, when two threads simultaneously
increment the reference count of the same object, the reference count
could end up being incremented only once instead of twice. Therefore,
the rule exists that only the thread that has acquired the global
interpreter lock may operate on Python objects or call Python/C API
functions. In order to support multi-threaded Python programs, the
interpreter regularly releases and reacquires the lock -- by default,
every 100 bytecode instructions (this can be changed with
sys.setcheckinterval())."

That doesn't mean you can't develop scalable solutions to all kinds of
problems using Python. But it does mean that the scalability of the
overall solution comes from architectural details that are not related
to Python itself. Like, say, having lots of machines linked by a fast
network, working on problems that decompose along those lines quite
nicely.
 
K

Ken Tilton

David said:
I'm glad you said that - this may be what he meant, but it seems
more plausible than what he actually said.

There may be some confusion here because there are two places for code
being discussed at the same time, and two sense of propagation.

the two places for code are (1) the rule attached to A which is
responsible for computing a value for A and (2) a callback for A to be
invoked whenever A changes. Why the difference?

In Cells, A is a slot such as 'background-color'. Whenever that changes,
we have to do something more. On Mac OS9 it was "InvalidateRect" of the
widget. In Cells-Tk, it is:
(Tcl_interp "mywidget configure -background <new color>")

In my OpenGL GUI, it is to rebuild the display-list for the widget.

That is the same no matter what rule some instance has for the slot
background-color, and different instances will have different rules.

As for propagating, yes, Cells propagates automatically. More below on
that. What I saw in the example offered was a hardcoded on-change
callback that was doing /user/ propagation form B to A (and B to A! ...
doesn't that loop, btw? Anyway...)
all
the *programmer* has to care about is what values a slot depends on --
Cells takes care of inverting that for you, which is important because
that's a job that a computer is much better at than a human.


Fine. I suppose that is better; if b is going to return a + 1
the fact that this is what b returns should belong to b, not
to a. So a has an update list including b, so when a's value
is set a tells b it needs to update itself.

If we're allowed to pass (at some point, to some constructor
or other) something like (b, a + 1, [a]), which sets up a
cell b that shall return a + 1, and where the [a] is used
in the constructor to tell a to add b to a's update list
then this seems like no big deal.

And doing that doesn't seem so bad - now when the programmer
is writing b he has to decide that it should return a + 1
and also explicitly state that b shall depend on a; this
is all nice and localized, it's still _b_ telling _a_ to
add b to a's update list, and the programmer only has
to figure out what _b_ depends on when he's writing _b_.
Doesn't seem so bad.

But of course it would be better to be able to pass just
something morally equivalent to (b, a + 1) to whatever
constructor and have the system figure out automatically
that since b returns a + 1 it has to add a to b's update
list. There must be some simple trick to accomplish that
(using Python, without parsing code).

Right, you do not want to parse code. It really would not work as
powerfully as Cells, which notice any dynamic access to another cell
while a rule is running. So my rule can call a function on "self" (the
instance that wons the slot being calculated, and since self can have
pointers to other instances, the algorithm can navigate high and low
calling other functions before finally reading another ruled slot. You
want to track those.
Exactly what the trick is I
don't see immediately.

To compute a value for a slot that happens to have a rule associated
with it, have a little cell datastructure that implements all this and
associate the cell with the slot and store a pointer to the rule in the
cell. Then have a global variable called *dependent* and locally:

currentdependent = *dependent*
oldvalue = cell.value
newvalue = call cell.rule, passing it the self instance
*dependent* = currentvalue

if newvalue not = oldvalue
call on-change on the slot name, self, newvalue and oldvalue
(the on-chnage needs to dispatch on as many arguments as
the language allows. Lisp does it on them all)

In the reader on a slot (in your getattr) you need code that notices if
the value being read is mediated by a ruled cell, and if the global
*dependent* is non empty. If so, both cells get a record of the other
(for varying demands of the implementation).
In Cells do we just pass a rule using other cells to
determine this cell's value, or do we also include
an explicit list of cells that this cell depends on?

Again, the former. Just write the rule, the above scheme dynamically
figures out the dependencies. Note then that dependencies vary over time
because of different branches a rule might take.

I want to reassure the community that this (nor the spreadsheet analogy
<g>) is not just my crazy idea. In 1992:

http://www.cs.utk.edu/~bvz/active-value-spreadsheet.html

"It is becoming increasingly evident that imperative languages are
unsuitable for supporting the complicated flow-of-control that arises in
interactive applications. This paper describes a declarative paradigm
for specifying interactive applications that is based on the spreadsheet
model of programing. This model includes multi-way constraints and
action procedures that can be triggered when constraints change the
values of variables."

Cells do not do multi-way constraints, btw. Nor partial constraints. To
hard to program, because the system gets non-deterministic. That kinda
killed (well, left to a small niche) the whole research programme. I
have citations on that as well. :)

kenny

--
Cells: http://common-lisp.net/project/cells/

"Have you ever been in a relationship?"
Attorney for Mary Winkler, confessed killer of her
minister husband, when asked if the couple had
marital problems.
 
A

Alex Martelli

Joe Marshall said:
If you language allows unnamed integers, unnamed strings, unnamed
characters, unnamed arrays or aggregates, unnamed floats, unnamed
expressions, unnamed statements, unnamed argument lists, etc. why
*require* a name for trivial functions?

I think it's reasonable to make a name a part of functions, classes and
modules because they may often be involved in tracebacks (in case of
uncaught errors): to me, it makes sense to let an error-diagnosing
tracebacks display packages, modules, classes and functions/methods
involved in the chain of calls leading to the point of error _by name_.

I think it's reasonable to make a name a part of types for a different
reason: new types are rarely meant to be used "just once"; but also, if
during debugging any object is displayed, it's nice to be able to show,
as part of the display, "this object is of type X and ...", with X shown
as a name rather than as a complete (thus lengthy) description. (any
decent interactive shell/debugger will let you drill down into the
details as and when you need to, of course, but a well-chosen name can
be often sufficient during such interactive exploration/debugging
sessions, and therefore save time and effort).

This doesn't stop a programmer from using a meaningless name, of course,
but it does nudge things in the right direction.
Wouldn't all the other constructs benefit by having a required name as
well?

I believe this is a delicate style call, but I agree with your
implication that a language should at least _allow_ any object to have a
name (even when such objects are more often constructed on the fly, they
could still usefully borrow the first [or, maybe, the latest] name
they're bound to, if any). If I was designing a language from scratch,
I'd probably have as the first few fields of any object _at least_...:
a cell pointing to the type object,
a utility cell for GC (reference count or generation-count +
markflag)
a cell pointing to the name object,
...rest of the object's value/state to follow...

Indeed, "given an object, how do I get its NAME" (for inspection and
debugging purposes) is the most frequently asked question on
comp.lang.python, and I've grown a bit tired of answering "you can't, an
object in general intrinsically ``has no name'', it might have many or
none at all, blah blah" -- yeah, this is technically true (in today's
Python), but there's no real reason why it should stay that way forever
(IMHO). If we at least ALLOWED named objects everywhere, this would
further promote the use of names as against mysterious "magic numbers",
since the programmer would KNOW that after
VAT_MULTIPLIER = 1.19
then displaying in a debugger or other interactive session that
PARTICULAR instance of the value 1.19 would show the name string
'VAT_MULTIPLIER' as well (or no doubt a more structured name constructed
on the fly, identifying package and module-within-package too).

As to what good practices should be more or less mandated by the
language, and what other good practices instead should be just gently
nudged towards, that's an interesting design question in each case; to
me, a cornerstone for answering it is generally _language simplicity_.

When mandating a certain good practice DETRACTS from language
simplicity, make it a matter of convention instead; when so mandating
ENHANCES language simplicity (by not needing the addition of some other
construct, otherwise unneeded in the language), go for the mandate.

Mandating names for _everything_ would complicate the language by
forcing it to provide builtin names for a lot of elementary building
blocks: so for most types of objects it's best to "gently nudge". For
functions, classes, modules, and packages, I think the naming is
important enough (as explained above) to warrant a syntax including the
name; better, therefore, not to complicate the language by providing
another different syntax in each case just to allow the name to be
omitted -- why encourage a pratice that's best discouraged, at the price
of language simplicity? This DOES imply that some (functions, modules,
etc) that are fundamental to the language (and needed to build others)
should be provided with a name, but then one tends to do that anyway:
what language *DOESN'T* provide (perhaps in some suitable "trigonometry"
module) elementary functions named (e.g.) sin, cos, tan, ..., to let the
user build richer ones on top of those?


Alex
 
P

Pisin Bootvong

Joe said:
Alex Martelli wrote:
Most languages allow `unnamed numbers'. The `VAT_MULTIPLIER' argument
is a
strawman. Would you want to have to use a special syntax to name the
increment
in loop?

defnumber zero 0
defnumber one { successor (zero); }

for (int i = zero; i < limit; i += one) { ...}

If you language allows unnamed integers, unnamed strings, unnamed
characters, unnamed arrays or aggregates, unnamed floats, unnamed
expressions, unnamed statements, unnamed argument lists, etc. why
*require* a name for trivial functions?
Wouldn't all the other constructs benefit by having a required name as
well?

Is this a Slippery Slope fallacious argument?
(http://c2.com/cgi/wiki?SlipperySlope)

"if python required you to name every function then soon it will
require you to name every number, every string, every immediate result,
etc. And we know that is bad. Therefore requiring you to name your
function is bad!!!! So Python is bad!!!!"


How about:

If Common Lisp lets you use unnamed function, then soon everyone will
start not naming their function. Then soon they will start not naming
their variable, not naming their magic number, not naming any of their
class, not naming any function, and then all Common Lisp program will
become one big mess. And we know that is bad. So allowing unnamed
function is bad!!!! So Common Lisp is bad!!!!!
 
K

Ken Tilton

Pisin said:
Is this a Slippery Slope fallacious argument?
(http://c2.com/cgi/wiki?SlipperySlope)

"if python required you to name every function then soon it will
require you to name every number, every string, every immediate result,
etc. And we know that is bad. Therefore requiring you to name your
function is bad!!!! So Python is bad!!!!"


How about:

If Common Lisp lets you use unnamed function, then soon everyone will
start not naming their function. Then soon they will start not naming
their variable, not naming their magic number, not naming any of their
class, not naming any function, and then all Common Lisp program will
become one big mess. And we know that is bad. So allowing unnamed
function is bad!!!! So Common Lisp is bad!!!!!

Funny you should mention that. Cells (obviously) have turned out to be a
gold mine for me in terms of development. And they have exactly one
non-limiting limitation: once you start using them, you have to use them
almost everywhere, because they create a different dataflow, or better
put, the dataflow replaces the control flow of imperative programming,
so there is no way to add a little subsection of functionality with
imperative code because all the action is over in dataflow-land.

I call it non-limiting because it is more like a healthy discipline: as
a consequence, all application semantics end up expressed as so many
discrete little cell rules. Which brings me to the punch line...

try to debug an app where all the code is in anonymous functions!!!

well, it was not a complete disaster because by hook or by crook one
could figure out which rule was at fault even in the worst case, and
most of the time there was not much question. But still...

well, it took me embarrasingly long to notice something. I never
actually coded (lambda (self) yada yada) for a rule. I always used a
macro: (c? yada yada)

This was great because it is more succinct and because once I had a
couple hundred of these i had little problem making serious overhauls to
the implementation. And of course if you know Lisp and macros...

duhhhhh! They operate on the code! So in two seconds I added a new slot
to a Cell called "code", and part of the macro expansion was to stuff
the source code into the code slot. ie...

The code: (c? (yada yada yada))
Becomes:
(make-c-dependent
:code '((yada yada yada))
:value-state :unevaluated
:rule (c-lambda (yada yada yada)))

c-lambda? I have a few of those c? macros, so I "submacro" the necessary
lambda form:

(lambda (slot-c &aux (self (c-model slot-c)) (.cache (c-value slot-c)))
(declare (ignorable .cache self))
(yada yada yada))

I almost never have to look at that code slot (as I said, most of the
time I can tell from the instance class and the slot name which rule I
screwed up) but when I am stumped, I just inspect the source code. :)

Oh, wait, this is not the "Should Python have macros" thread, is it?

:)

kenny

--
Cells: http://common-lisp.net/project/cells/

"Have you ever been in a relationship?"
Attorney for Mary Winkler, confessed killer of her
minister husband, when asked if the couple had
marital problems.
 
A

Alex Martelli

Patrick May said:
It seems that someone pisses in your cornflakes nearly every
morning.

For the record, I was attempting to respond to your post which I
only saw quoted in another message. Please excuse any accidental
misquoting.

Your message was an immediate followup to mine, but all the text you
quoted in it was by Brian (w/o mentioning him) -- you quoted no text
written by me.
That claim is, frankly, silly. A function is far more
understandable without a name than a value like 1.19 in isolation.
The situations aren't remotely comparable.

I think the comparability is definitely there. Somebody asked me about
translating a bunch of Lisp he'd written (he later admitted he had
misunderstood the power of python's def, and that it lets one do all
he's using unnamed functions for); well, each of those HOF's returns an
unnamed function *with a nice short comment explaining WHAT it does*.

The code would be of far worse quality without the nice short comments,
but it would be much better if the comments were turned into *NAMES*
(allowing easier inspection in interactive development, debugging
including examination of tracebacks, etc). What's the *POINT* of coding
(python syntax):

def blank(*a):
" return a blank picture "
return lambda *ignore_args: []

rather than:

def blank(*a):
def blank_picture(*ignore_args): return []
return blank_picture

and so forth? The former is obscure (ok, it's an anonymous function
taking and ignoring arbitrary args and returning an empty list, but WHY,
WHAT DOES IT MEAN?!), except for the explanatory comment; the latter
clearly defines the purpose of the returned-function by its name. The
situation is exactly parallel to "magic numbers", as in:

total *= 1.19

is entirely mysterious (OK, total, is being multiplied by 1.19, but WHY,
WHAT DOES IT MEAN?!), better is

# augment total by VAT
total *= 1.19

and better still

VAT_MULTIPLIER = 1.19
total *= VAT_MULTIPLIER

A comment is better than nothing (given that the 1.19 constant, or the
function ignoring its arguments and returning empty list, are mysterious
in their purpose), a name is better still.

Like Scheme?

Didn't want to trigger some flamewar;-), but, yes, if that was my only
choice, I'd much rather use small, simple Scheme than huge, complicated,
rich, powerful Common Lisp. ((But in this case I'm biased by early
experiences, since when I learned and used Lisp-ish languages there WAS
no Common Lisp, while Scheme was already there, although not quite the
same language level as today, I'm sure;-)).


Alex
 
A

Alex Martelli

Cameron Laird said:
On this one isolated matter, though, I'm confused, Alex: I sure
think *I* have been writing DSLs as specializations of Python,
and NOT as "a language in its own right". Have I been fooling
myself, or are you making the point that Lisp-based DSLs live in
a larger syntactic universe than Python's, or ...?

With Lisp (or Scheme, for that matter), a DSL "lives" in exactly the
same world as the base language, while being able to add or tweak
whatever syntax it needs; with Python (or C++ or Java, for that matter),
a DSL is either a completely separate beast (parsed, compiled, perhaps
interpreted in the "host" language), or else it uses exactly the same
syntax as used in the host language. To rapidly build, experiment with,
and tweak, DSL's, a language with macros is thus advantaged.

As to how crucial that is for _production_ (as opposed to _research_)
purposes, well, obviously I prefer having no macros (or else I'd use
some form of Lisp, or perhaps Dylan -- at least for my own private
purposes, such as my long-standing personal research into the
fundamentals of contract bridge -- while in fact I prefer to use Python
for those purposes just as for others). But that doesn't make me blind
to the advantages of macros for DSL-building purposes (if I was totally
sold on both Python AND macros, I think I might build a macro
preprocessor on top of Python -- the current ASL-based compiler probably
makes that task much more feasible than it used to be -- but, macros
would be somewhat complicated as in Dylan, not extremely easy as in
Scheme [[or, I guess, Common Lisp, though I have no real experience with
those -- on the surface they look murkier than Scheme's, but that may be
just an issue of habit on my part]]).


Alex
 
A

Alex Martelli

Joe Marshall said:
Doesn't Google also employ such people as the inventor of Limbo
programming language, one of the inventors of Dylan, and a Smalltalk
expert?

....not to mention Lisp gurus (such as Peter Norvig), C++ gurus (such as
Matt Austern) and Java ones (such as Josh Bloch) [[and I'm certainly
forgetting many!]].

The difference, if any, is that gurus of Java, C++ and Python get to
practice and/or keep developing their respectively favorite languages
(since those three are the "blessed" general purpose languages for
Google - I say "general purpose" to avoid listing javascript for
within-browser interactivity, SQL for databases, XML for data
interchange, HTML for web output, &c, &c), while the gurus of Lisp,
Limbo, Dylan and Smalltalk don't (Rob Pike, for example, is one of the
architects of sawzall -- I already pointed to the whitepaper on that
special-purpose language, and he co-authored that paper, too).


Alex
 
M

M Jared Finder

Alex said:
I think it's reasonable to make a name a part of functions, classes and
modules because they may often be involved in tracebacks (in case of
uncaught errors): to me, it makes sense to let an error-diagnosing
tracebacks display packages, modules, classes and functions/methods
involved in the chain of calls leading to the point of error _by name_.

I think it's reasonable to make a name a part of types for a different
reason: new types are rarely meant to be used "just once"; but also, if
during debugging any object is displayed, it's nice to be able to show,
as part of the display, "this object is of type X and ...", with X shown
as a name rather than as a complete (thus lengthy) description. (any
decent interactive shell/debugger will let you drill down into the
details as and when you need to, of course, but a well-chosen name can
be often sufficient during such interactive exploration/debugging
sessions, and therefore save time and effort).

Any time you want an anonymous function (or class, or type, or number)
it would be because that thing is sufficiently small and simple that the
best name for it is the code itself. In one game I worked on, there was
a function named canPerformAction_and_isNotActionInQueue. It was a
simple, one line function:

bool canPerformAction_and_isNotActionInQueue( Action action ) {
return canPerformAction( action ) && !isActionInQueue( action );
}

There was no better, more abstract name, as the design required this
logic for a completely arbitrary reason -- so arbitrary it changed
multiple times in development. For a little while it was used in two
places. Then one of those places changed to have only the
isActionInQueue part. There was no useful abstraction to be made, and
it is in cases like these (which come up a lot when using functions as
parameters) where anonymous functions are a win.

-- MJF
 
A

Alex Martelli

M Jared Finder said:
Any time you want an anonymous function (or class, or type, or number)
it would be because that thing is sufficiently small and simple that the
best name for it is the code itself. In one game I worked on, there was

That's not what I see happen in practice in the real world -- please
check this thread for the guy who pointed me at some Lisp code of his to
draw pictures, and how each anonymous function is code returned had a
nice little comment (which would yield a perfectly suitable name, as I
showed in a Python translation of one of them). In the real world,
people don't choose anonymous functions only in these alleged cases
where anonymous is best -- if anonymous functions are available, they're
used in even more cases where naming would help (just as, again in the
real world, plenty of "magic numbers" sully the code which SHOULD be
named... but just don't GET named).

BTW, in your case canPerformQueuedAction seems a good name to me (I'd
probably eliminate the 'Action' part, since the _argument_ is an Action,
but I'd need to see more context to suggest the best name).


Alex
 

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
473,813
Messages
2,569,696
Members
45,479
Latest member
QFZErin313

Latest Threads

Top