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

C

Chris F Clark

Kenny replied to me saying:
Yep. But with Cells the dependency graph is just a shifting record of
who asked who, shifting because all of a sudden some outlier data will
enter the system and a rule will branch to code for the first time,
and suddenly "depend on" on some new other cell (new as in never
before used by this cell). This is not subject to static analysis
because, in fact, lexically everyone can get to everything else, what
with closures, first-class functions, runtime branching we cannot
predict... fuggedaboutit.

So we cannot say, OK, here is "the graph" of our application
model. All we can do is let her rip and cross our fingers. :)

Yes, if you have Turing completeness in your dependency graph, the
problem is unsolvable. However, it's like the static v. dynamic
typing debate, you can pick how much you want to allow your graph to
be dynamic versus how much "safety" you want. In particular, I
suspect that in many applications, one can compute the set of
potentially problematic dependencies (and that set will be empty).
It's just a matter of structuring and annotating them correctly. Just
like one can create type systems that work for ML and Haskell. Of
course, if you treat your cell references like C pointers, then you
get what you deserve.

Note that you can even run the analysis dynamically, recomputing
whether the graph is cycle free as each dependency changes. Most
updates have local effect. Moreover, if you have used topological
sort to compute an ordering as well as proving cycle-free-ness, the
edge is only pontentially problemantic when it goes from a later
vertex in the order to an earlier one. I wouldn't be surprised to
find efficient algorithms for calculating and updating a topological
sort already in the literature.

It is worth noting that in typical chip circuitry there are
constructions, generally called "busses" where the flow of information
is sometimes "in" via an edge and sometimes "out" via the same edge
and we can model them in a cycle-free manner.

If you want to throw up your hands and say the problem is intractable
in general, you can. However, in my opinion one doesn't have to give
up quite that easily.

-Chris
 
F

Frank Goenninger DG1SBG

Ken Tilton said:
Ah, I was guilty of making an unspoken segue: the problem is not with
the *dependent* special variable, but with the sequentially growing
numeric *datapulse-id* ("the ID") that tells a cell if it needs to
recompute its value. The ID is not dynamically bound. If threads T1
and T2 each execute a toplevel, imperative assignment, two threads
will start propagating change up the same dependency
graph... <shudder>

Might need to specify a "main" thread that gets to play with Cells and
restrict other threads to intense computations but no Cells?

Hmmm. I am wondering if a Cells Manager class could be the home for
all Cells. Each thread could the have its own Cells Manager...
Actually, I got along quite a while without an ID, I just propagated
to dependents and ran rules. This led sometimes to a rule running
twice for one change and transiently taking on a garbage value, when
the dependency graph of a Cell had two paths back to some changed
Cell.

Well, Cells have always been reengineered in the face of actual use
cases, because I am not really smart enough to work these things out
in the abstract. Or too lazy or something. Probably all three.

Nah. It's me asking again and again those silly questions about
real Cells usage in some real life apps ;-)

Frank
 
A

Alex Martelli

Joe Marshall said:
Can you refer to inner functions from the global context? Suppose I
have this Python code:

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

Can I refer to the inner adder_func in any meaningful way?

You can refer to one instance/closure (which make_adder returns), of
course -- you can't refer to the def statement itself (but that's a
statement, ready to create a function/closure each time it executes, not
a function, thus, not an object) except through introspection. Maybe I
don't understand what you mean by this question...

I meant continuations as in the receiver function in
continuation-passing-style. If you have a function that has to act
differently in response to certain conditions, and you want to
parameterize the behavior, then one possibility is to pass one or more
thunks to the function in addition to the normal arguments. The

Ah, OK, I would refer to this as "callbacks", since no
call-with-continuation is involved, just ordinary function calls; your
use case, while pretty alien to Python's typical style, isn't all that
different from other uses of callbacks which _are_ very popular in
Python (cfr the key= argument to the sort methods of list for a typical
example). I would guess that callbacks of all kinds (with absolutely
trivial functions) is the one use case which swayed Guido to keep lambda
(strictly limited to just one expression -- anything more is presumably
worth naming), as well as to add an if/else ternary-operator. I still
disagree deeply, as you guessed I would -- if I had to work with a
framework using callbacks in your style, I'd name my callbacks, and I
wish Python's functools module provided for the elementary cases, such
as:

def constant(k):
def ignore_args(*a): return k
return ignore_args

def identity(v): return v

and so on -- I find, for example, that to translate your
(define (option3 key table default-value)
(lookup key table
(lambda (value) value)
(lambda () default-value)))

I prefer to use:

def option3(key, table, default_value):
return lookup(key, table, identity, constant(default_value))

as being more readable than:

def option3(key, table, default_value):
return lookup(key, table, lambda v: v, lambda: default_value)

After all, if I have in >1 place in my code the construct "lambda v: v"
(and if I'm using a framework that requires a lot of function passing
I'm likely to be!), the "Don't Repeat Yourself" (DRY) principle suggests
expressing the construct *ONCE*, naming it, and using the name.

By providing unnamed functions, the language aids and abets violations
of DRY, while having the library provide named elementary functions (in
the already-existing appropriate module) DRY is reinforced and strongly
supported, which, IMHO, is a very good thing.


Alex
 
K

Ken Tilton

Alex said:
That would _complicate_ the language (by adding a rule). I repeat what
I've already stated repeatedly: a good criterion for deciding which good
practices a language should enforce and which ones it should just
facilitate is _language simplicity_. If the enforcement is done by
adding rules or constructs it's probably not worth it; if the
"enforcements" is done by NOT adding extra constructs it's a double win
(keep the language simpler AND push good practices).

Gosh, that looks like fancy footwork. But maybe I misunderstand, so I
will just ask you to clarify.

In the case of (all syntax imaginary and not meant ot be Python):

if whatever = 42
dothis
do that
do something else
else
go ahead
make my day

You do not have a problem with unnamed series of statements. But in the
case of:

treeTravers( myTree, lambda (node):
if xxx(node)
print "wow"
return 1
else
print "yawn"
return 0)

....no, no good, you want a named yawnOrWow function? And though they
look similar, the justification above was that IF-ELSE was lucky enough
to get multiline branches In the Beginning, so banning it now would be
"adding a rule", whereas lambda did not get multiline In the Beginning,
so allowing it would mean "adding a construct". So by positing "adding a
rule or construct" as always bad (even if they enforce a good practice
such as naming an IF branch they are bad since one is /adding/ to the
language), the inconsistency becomes a consistency in that keeping IF
powerful and denying lambda the same power each avoids a change?

In other words, we are no longer discussing whether unnamed multi-line
statements are a problem. The question is, would adding them to lambda
mean a change?

Oh, yeah, it would. :)

hth, 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.
 
M

M Jared Finder

Alex said:
Except that the major premise is faulty! Try e.g.
<http://docs.sun.com/app/docs/doc/817-5477/6mkuavhrf#hic> and count the
number of distinct instructions -- general purpose, floating point,
SIMD, MMX, SSE, SSE2, OS support... there's *hundreds*, each with its
own rules as to what operand(s) are allowed plus variants such as (e.g.)
cmovbe{w,l,q} for "conditional move if below or equal" for word, long,
quadword (no byte variant) -- but e.g cmpxchg{b,w,l,q} DOES have a byte
variant too, while setbe for "set if below or equal" ONLY has a byte
variant, etc, etc -- endless memorization;-).

When you set up your strawman arguments, try to have at least ONE of the
premises appear sensible, will you?-)

I never argued against keeping languages at a high level, of course
(that's why your so utterly unfounded argument would be a "strawman"
even if it WAS better founded;-).


Adding one construct (e.g., in Python, having both def and lambda with
vast semantic overlap, rather than just one) cannot "make the language
simpler to implement" -- no doubt this kind of "reasoning" (?) is what
ended up making the instruction-set architecture of the dominant
families of CPUs so bizarre, intricate, and abstruse!-)

It sure can. First, let's cover the cost. I'll be measuring everything
in terms of lines of code, with the assumption that the code has been
kept readable.

Here's an implementation of lambda (anonymous functions) in Lisp based
on flet (lexically scoped functions):

(defmacro lambda (args &rest body)
(let ((name (gensym)))
`(flet ((,name ,args ,@body)) (function ,name))))

That's three lines of code to implement. An almost trivial amount.

Now by using anonymous functions, you can implement many other language
level features simpler. Looping can be made into a regular function
call. Branching can be made into a regular function call. Defining
virtual functions can be made into a regular function call. Anything
that deals with code blocks can be made into a regular function call.

By removing the special syntax and semantics from these language level
features and making them just pain old function calls, you can reuse the
same evaluator, optimizer, code parser, introspector, and other code
analyzing parts of your language for these (no longer) special
constructs. That's a HUGE savings, well over 100 lines of code.

Net simplification, at least 97 lines of code. For a concrete example
of this in action, see Smalltalk.

-- MJF
 
M

M Jared Finder

Chris said:
E.g. can you add three-way comparisons (less-than, same-as, greater-than to,
say, Python with corresponding three-way conditional control structures to
supplement "if" etc ? Are they on a semantic and syntactic par with the
existing ones ? In Smalltalk that is trivial (too trivial to be particularly
interesting, even), and I presume the same must be true of Lisp (though I
suspect you might be forced to use macros).

As an illustration, here's the definition and usage of such a numeric-if
in Lisp.

Using raw lambdas, it's ugly, but doable:

(defun fnumeric-if (value lt-body eq-body gt-body)
(cond ((< value 0) (funcall lt-body))
((= value 0) (funcall eq-body))
((> value 0) (funcall gt-body))))

(fnumeric-if (- a b)
(lambda () (print "a < b"))
(lambda () (print "a = b"))
(lambda () (print "a > b")))

A macro helps clean that up and make it look prettier:

(defmacro numeric-if (value lt-body eq-body gt-body)
`(fnumeric-if ,value
(lambda () ,lt-body)
(lambda () ,eq-body)
(lambda () ,gt-body)))

(numeric-if (- a b)
(print "a < b")
(print "a = b")
(print "a > b"))

-- MJF
 
M

Michele Simionato

jayessay said:
I was saying that you are mistaken in that pep-0343 could be used to
implement dynamically scoped variables. That stands.

Proof by counter example:

from __future__ import with_statement
import threading

special = threading.local()

def getvar(name):
return getattr(special, name)

def setvar(name, value):
return setattr(special, name, value)

class dynamically_scoped(object):
def __init__(self, name, value):
self.name = name
self.value = value
def __context__(self):
return self
def __enter__(self):
self.orig_value = getvar(self.name)
setvar(self.name, self.value)
def __exit__(self, Exc, msg, tb):
setvar(self.name, self.orig_value)

if __name__ == '__main__': # test
setvar("*x*", 1)
print getvar("*x*") # => 1
with dynamically_scoped("*x*", 2):
print getvar("*x*") # => 2
print getvar("*x*") # => 1

If you are not happy with this implementation, please clarify.

Michele Simionato
 
P

Petr Prikryl

Chris Uppal said:
Petr said:
for element in aCollection:
if element > 0:
return True
return False

[I'm not sure whether this is supposed to be an example of some specific
language (Python ?) or just a generic illustration. I'll take it as the
latter, since it makes my point easier to express. I'll also exaggerate, just
a little...]

Sorry, I do not know Smalltalk, but this was meant as the transcription
of your...
| E.g. consider the Smalltalk code (assumed to be the body of a method):
|
| aCollection
| do: [:each |
| each > 0 ifTrue: [^ true]].
| ^ false.

into Python
But now, in order to hack around the absence of a sensible and useful
feature -- /only/ in order to do so -- you have added two horrible new
complications to your language. You have introduced a special syntax to
express conditionals, and (worse!) a special syntax to express looping. Not
only does that add a huge burden of complexity to the syntax, and semantics, of
the language (and, to a lesser extent, its implementation), but it also throws
out any semblance of uniformity.

I guess that it is not me who is confused here. The subject clearly
says that the thread is related to Python and to lambda supported
by Python. It was only crossposted to other groups and I did
not want to remove them -- other people may want to read
the thread in the other newsgroups.

So, I did not introduced any horible syntax, nor looping
construct that would look strange to people used to
classical procedural languages. The lambda syntax
in Python is the thing that could be viewed as a complication,
not the "for" loop or the "if" construction.

If you take any English speaking human (even the non-programmer),
I could bet that the Python transcription will be more understandable
than your Smalltalk example.
E.g. in Java there's an unresolved, and irresolvable, tension between whether a
failing operation should return an error condition or throw an exception
[...].

It is more a design problem than the language problem. And it is also
the implementation problem (i.e. what is the price of exceptions
in comparison with the other code). In Python, the exceptions
are intesively used.
E.g. can you add three-way comparisons (less-than, same-as, greater-than to,
say, Python with corresponding three-way conditional control structures to
supplement "if" etc ? Are they on a semantic and syntactic par with the
existing ones ? In Smalltalk that is trivial (too trivial to be particularly
interesting, even), and I presume the same must be true of Lisp (though I
suspect you might be forced to use macros).

Such built-in function already is in Python. But you could add it
by hand if it were not:

def cmp(x, y):
if x < y:
return -1
if x == y:
return 0
return 1

and the "if" supplement (the "switch" or "case" command) could be
replaced easily in Python using the hash-table (dictionary) structure.
I should say that if your example /is/ in fact Python, then I believe that
language allows fairly deep hooks into the execution mechanism, so that at
least the "for" bit can be mediated by the collection itself -- which is better
than nothing, but nowhere near what I would call "good".

It is a dual point of view. Should the collection be passive data, or not?
I believe that the "pure" object oriented view (there are no functions,
only the object methods) is not very practical and does not reflect
well the big part of the reality that is simulated by programs.
Python and C++, for example, allow mixing functions and objects.

You should try Python. The "for" construct iterates through
a sequence or through all values of the generator, thus making
the for loop much more generic than for example in C or other
languages.

Every language forms the way of thinking. Every language has its strong
and weak points. Every language has its followers and haters.
Not every language is practical enough to fly around the Earth
in the space ship.

pepr

(Sorry for my broken English.)
 
T

Thomas F. Burdick

Ken Tilton said:
Right, when I considered multi-way dependencies I realized I would
have to figure out some new syntax to declare in one place the rules
for two slots, and that would be weird because in Cells it is the
instance that gets a rule at make-instance time, so i would really
have to have some new make-instance-pair capability. Talk about a
slippery slope. IMO, the big constraints research program kicked off
by Steele's thesis withered into a niche technology because they
sniffed at the "trivial" spreadsheet model of linear dataflow and
tried to do partial and multi-way dependencies. I call it "a bridge
too far", and in my experience of Cells (ten years of pretty intense
use), guess what?, all we need as developers is one-way, linear,
fully-specified dependencies.

It may also be that the bridge too far was in trying to do big,
multi-way constraints in a general-purpose manner. Cells provides you
with the basics, and you can build a special-purpose multi-way system
on top of it, much like you can use it as a toolkit for doing KR.
 
J

jayessay

Michele Simionato said:
Proof by counter example:

from __future__ import with_statement
import threading

special = threading.local()

def getvar(name):
return getattr(special, name)

def setvar(name, value):
return setattr(special, name, value)

class dynamically_scoped(object):
def __init__(self, name, value):
self.name = name
self.value = value
def __context__(self):
return self
def __enter__(self):
self.orig_value = getvar(self.name)
setvar(self.name, self.value)
def __exit__(self, Exc, msg, tb):
setvar(self.name, self.orig_value)

if __name__ == '__main__': # test
setvar("*x*", 1)
print getvar("*x*") # => 1
with dynamically_scoped("*x*", 2):
print getvar("*x*") # => 2
print getvar("*x*") # => 1

If you are not happy with this implementation, please clarify.

I can't get this to work at all - syntax errors (presumably you must
have 2.5?, I only have 2.4). But anyway:

This has not so much to do with WITH as relying on a special "global"
object which you must reference specially, which keeps track (more or
less) of its attribute values, which you use as "faked up" variables.
Actually you probably need to hack this a bit more to even get that as
it doesn't appear to stack the values beyond a single level.


/Jon
 
B

Boris Borcic

jayessay said:
I can't get this to work at all - syntax errors (presumably you must
have 2.5?, I only have 2.4). But anyway:

This has not so much to do with WITH as relying on a special "global"
object which you must reference specially, which keeps track (more or
less) of its attribute values, which you use as "faked up" variable
Actually you probably need to hack this a bit more to even get that as
it doesn't appear to stack the values beyond a single level.

Actually there's no problem there. hint : dynamically_scoped is a class that the
with statement will instantiate before (any) entry. OTOH, as it is written, I am
not convinced it will work in a multithreaded setting : isn't it the case that
all threads that will import eg dynamically_scoped/getvar/setvar will act
without sync on the /single/ special object of the /single/ thread that
initialized the module ?

But I'm not sure, it's been ages since I used python threading.
 
M

Michele Simionato

jayessay said:
I can't get this to work at all - syntax errors (presumably you must
have 2.5?, I only have 2.4).

You can download Python 2.5 from www.python.org, but the important bit,
i.e. the use of threading.local to get thread-local variables is
already there in Python 2.4.
'with' gives you just a nicer lisp-like syntax.
This has not so much to do with WITH as relying on a special "global"
object which you must reference specially, which keeps track (more or
less) of its attribute values, which you use as "faked up" variables.
Actually you probably need to hack this a bit more to even get that as
it doesn't appear to stack the values beyond a single level.

Yes, but it would not be difficult, I would just instantiate
threading.local inside
the __init__ method of the dynamically_scoped class, so each 'with'
block
would have its own variables (and I should change getvar and setvar a
bit).

I was interested in a proof of concept, to show that Python can emulate
Lisp
special variables with no big effort.

Michele Simionato
 
J

jayessay

Michele Simionato said:
I was interested in a proof of concept, to show that Python can
emulate Lisp special variables with no big effort.

OK, but the sort of "proof of concept" given here is something you can
hack up in pretty much anything. So, I wouldn't call it especially
convincing in its effect and capability.


/Jon
 
A

Alexander Schmolck

jayessay said:
OK, but the sort of "proof of concept" given here is something you can
hack up in pretty much anything.

Care to provide e.g. a java equivalent?

'as
 
K

Ken Tilton

Michele said:
Proof by counter example:

from __future__ import with_statement
import threading

special = threading.local()

def getvar(name):
return getattr(special, name)

def setvar(name, value):
return setattr(special, name, value)

class dynamically_scoped(object):
def __init__(self, name, value):
self.name = name
self.value = value
def __context__(self):
return self
def __enter__(self):
self.orig_value = getvar(self.name)
setvar(self.name, self.value)
def __exit__(self, Exc, msg, tb):
setvar(self.name, self.orig_value)

if __name__ == '__main__': # test
setvar("*x*", 1)
print getvar("*x*") # => 1
with dynamically_scoped("*x*", 2):
print getvar("*x*") # => 2
print getvar("*x*") # => 1

If you are not happy with this implementation, please clarify.

Can you make it look a little more as if it were part of the language,
or at least conceal the wiring better? I am especially bothered by the
double-quotes and having to use setvar and getvar.

In Common Lisp we would have:

(defvar *x*) ;; makes it special
(setf *x* 1)
(print *x*) ;;-> 1
(let ((*x* 2))
(print *x*)) ;; -> 2
(print *x*) ;; -> 1

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.
 
K

Ken Tilton

Alexander said:
Care to provide e.g. a java equivalent?

I think the point is that, with the variable actually being just a
string and with dedicated new explicit functions required as
"accessors", well, you could hack that up in any language with
dictionaries. It is the beginnings of an interpreter, not Python itself
even feigning special behavior.

perhaps the way to go is to take the Common Lisp:

(DEFVAR *x*)

*x* = special_var(v=42) ;; I made this syntax up

that could make for cleaner code:

*x*.v = 1

print *x*.v -> 1

(Can we hide the .v?) But there is still the problem of knowing when to
revert a value to its prior binding when the scope of some WITH block is
left.

Of course that is what indentation is for in Python, so... is that
extensible by application code? Or would this require Python internals work?

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

Alexander Schmolck

Ken Tilton said:
In Common Lisp we would have:

(defvar *x*) ;; makes it special
(setf *x* 1)
(print *x*) ;;-> 1
(let ((*x* 2))
(print *x*)) ;; -> 2
(print *x*) ;; -> 1

You seem to think that conflating special variable binding and lexical
variable binding is a feature and not a bug. What's your rationale?

'as
 
D

Duane Rettig

Alexander Schmolck said:
You seem to think that conflating special variable binding and lexical
variable binding is a feature and not a bug. What's your rationale?

A bug is a non-conformance to spec. Kenny's statement was specifically
about Common Lisp, which has a spec. Now, what was your rationale for
it _being_ a bug?
 
A

Alexander Schmolck

Ken Tilton said:
I think the point is that, with the variable actually being just a string and
with dedicated new explicit functions required as "accessors", well, you could
hack that up in any language with dictionaries.

Great -- so can I see some code? Can't be that difficult, it takes about 10-15
lines in python (and less in scheme).
It is the beginnings of an interpreter, not Python itself even feigning
special behavior.


perhaps the way to go is to take the Common Lisp:

(DEFVAR *x*)

*x* = special_var(v=42) ;; I made this syntax up

that could make for cleaner code:

*x*.v = 1

print *x*.v -> 1

(Can we hide the .v?)

I'd presumably write special variable access as something like:

with specials('x','y','z'):
special.x = 3 + 4
special.y = special.x + 10
...

I haven't tested this because I haven't got the python 2.5 alpha and won't go
through the trouble of installing it for this usenet discussion, but I'm
pretty sure this would work fine (I'm sure someone else can post an
implementation or prove me wrong). I also can't see how one could sensibly
claim that this doesn't qualify as an implementation of dynamically scoped
variables. Doesn't look any worse to me than

(let (x y z)
(declare (special x y z))
...)

-- in fact it looks better.
But there is still the problem of knowing when to revert a value to its
prior binding when the scope of some WITH block is left.

Can you explain what you mean by this statement? I'm not quite sure but I've
got the impression you're a possibly confused. Have you had a look at
Of course that is what indentation is for in Python, so... is that extensible
by application code?

The meaning of indentation? No.
 
P

Paul Rubin

Alexander Schmolck said:
You seem to think that conflating special variable binding and lexical
variable binding is a feature and not a bug. What's your rationale?

I thought special variables meant dynamic binding, i.e.

(defvar *x* 1)
(defun f ()
(print *x*) ;; -> 2
(let ((*x* 3))
(g)))
(defun g ()
(print *x*)) ;; - > 3

That was normal behavior in most Lisps before Scheme popularlized
lexical binding. IMO it was mostly an implementation convenience hack
since it was implemented with a very efficient shallow binding cell.
That Common Lisp adapted Scheme's lexical bindings was considered a
big sign of CL's couthness. So I'm a little confused about what Ken
Tilton is getting at.
 

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,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top