A replacement for lambda

M

Mike Meyer

I know, lambda bashing (and defending) in the group is one of the most
popular ways to avoid writing code. However, while staring at some Oz
code, I noticed a feature that would seem to make both groups happy -
if we can figure out how to avoid the ugly syntax.

This proposal does away with the well-known/obscure "lambda"
keyword. It gives those who want a more functional lambda what they
want. It doesn't add any new keywords. It doesn't add any new magic
characters, though it does add meaning to an existing one. That could
be replaced by a new magic token, or adding magic meaning to a
non-magic token. It breaks no old code either way.

I haven't really worked out all the implications; I just wanted to
throw it out and see what everyone else thought about it. As a
result, the code examples tend to be ugly.

As previously hinted, this feature is lifted from Oz.

Currently, class and functions definitions consist of a keyword -
either "class" or "def" - followed by a name, a header, then code. The
code is compiled into an object, and the name is bound to that object.

The proposal is to allow name to be a non-name (or rare name)
token. In this case, the code is compiled and the resulting object is
used as the value of the class/def expression.

My choice for the non-name token is "@". It's already got magic
powers, so we'll give it more rather than introducing another token
with magic powers, as the lesser of two evils.

Rewriting a canonical abuse of lambda in this idiom gives:

myfunc = def @(*args):
return sum(x + 1 for x in args)

In other words, this is identical to:

def myfunc(*args):
return sum(x + 1 for x in args)

We can write the same loop with logging information as:

sum(def @(arg):
print "Bumping", arg
return arg + 1
(x) # '(' at the same indent level as def, to end the definition
for x in stuff)

A more useful example is the ever-popular property creation without
cluttering the class namespace:

class Spam(object):
myprop = property(fget = def @(self):
return self._properties['myprop']
,
fset = def @(self, value):
self._properties['myprop'] = value
,
fdel = def @(self)
del self._properties['myprop']
,
doc = "Just an example")

This looks like the abuse of lambda case, but these aren't
assignments, they're keyword arguments. You could leave off the
keywords, but it's not noticably prettier. fget can be done with a
lambda, but the the others can't.

Giving clases the same functionality seems to be the reasonable thing
to do. It's symmetric. And if anonymous function objects are good,
then anonymous class objects ought to be good as well.

<mike
 
C

Christopher Subich

Mike said:
My choice for the non-name token is "@". It's already got magic
powers, so we'll give it more rather than introducing another token
with magic powers, as the lesser of two evils.

Doesn't work. The crux of your change isn't introducing a meaning to @
(and honestly, I prefer _), it's that you change the 'define block' from
a compound_stmt (funcdef) (see
www.python.org/doc/current/ref/compound.html) to an expression_stmt
(expresion). This change would allow some really damn weird things, like:

if def _(x,y):
return x**2 - y**2
(5,-5): # ?! How would you immediately call this 'lambda-like'?[1]
print 'true'
else:
print 'false'

[1] -- yes, it's generally stupid to, but I'm just pointing out what has
to be possible.

Additionally, Python's indenting Just Doesn't Work Like That; mandating
an indent "after where the def came on the previous line" (as you do in
your example, I don't know if you intend for it to hold in your actual
syntax) wouldn't parse right -- the tokenizer generates INDENT and
DEDENT tokens for whitespace, as I understand it.

My personal favourite is to replace "lambda" entirely with an
"expression comprehension", using < and > delimeters. It just looks
like our existing list and generator comprehensions, and it doesn't use
'lambda' terminology which will confuse any newcomer to Python that has
experience in Lisp (at least it did me).

g = <x**2 with (x)>
g(1) == 1

Basically, I'd rewrite the Python grammar such that:
lambda_form ::= "<" expression "with" parameter_list ">"

Biggest change is that parameter_list is no longer optional, so
zero-argument expr-comps would be written as <expr with ()>, which makes
a bit more sense than <expr with>.

Since "<" and ">" aren't ambiguous inside the "expression" state, this
shouldn't make the grammar ambiguous. The "with" magic word does
conflict with PEP-343 (semantically, not syntactically), so "for" might
be appropriate if less precise in meaning.
 
P

Paul Rubin

Christopher Subich said:
My personal favourite is to replace "lambda" entirely with an
"expression comprehension", using < and > delimeters.

But how does that let you get more than one expression into the
anonymous function?
 
S

Scott David Daniels

Christopher said:
g = <x**2 with (x)>
g(1) == 1

Basically, I'd rewrite the Python grammar such that:
lambda_form ::= "<" expression "with" parameter_list ">"

Biggest change is that parameter_list is no longer optional, so
zero-argument expr-comps would be written as <expr with ()>, which makes
a bit more sense than <expr with>.

Since "<" and ">" aren't ambiguous inside the "expression" state, this
shouldn't make the grammar ambiguous. The "with" magic word does
conflict with PEP-343 (semantically, not syntactically), so "for" might
be appropriate if less precise in meaning.

What kind of shenanigans must a parser go through to translate:
<x**2 with(x)><<x**3 with(x)>

this is the comparison of two functions, but it looks like a left-
shift on a function until the second with is encountered. Then
you need to backtrack to the shift and convert it to a pair of
less-thans before you can successfully translate it.

--Scott David Daniels
(e-mail address removed)
 
J

James Richards

What kind of shenanigans must a parser go through to translate:
<x**2 with(x)><<x**3 with(x)>

this is the comparison of two functions, but it looks like a left-
shift on a function until the second with is encountered. Then
you need to backtrack to the shift and convert it to a pair of
less-thans before you can successfully translate it.

I'm just worming my way into learning Lisp, but this seems to be a
perfect example of everything I'm seeing so far. The compiler should
do all sorts of gymnastics and contortions. Make the compiler/interpreter
as complex as possible, to handle any freaky thing a programmer can
invent.

Which does not, in the least, imply that the same attitude should apply toward
python.

I read this thread and my brain hurts. Python code should *never* do this.

Tim Peters (let's all bow down, we're not worthy <g>) might write some code that
I can't quite follow. But at least it's obvious. He doesn't suddenly introduce
an entirely new syntax for the sake of "hey, this would be cool." Or maybe
"It'd be much more simple if we made everyone use more C-like syntax." Or...
wherever you were going with this. This
<x**2 with(x)><<x**3 with(x)>
is precisely the kind of code that I got into python to avoid.

I happen to like nice, simple, readable code. Maybe I'm just old and grumpy.
Looking at that line, I get the same "This is just ugly" feel that I get when
I at perl. Some note from other pieces in this thread. Things about $_ or
what-not.


Personally, I can't recall any decent programmer I know who objects to actually
writing out a variable name. In fact, I don't know a single "real" programmer
(this is one who writes programs he intends to look at again in, say, 3 weeks)
who doesn't insist on writing "real" variable names.

(Heh. I'll probably read through some Guido code next week that totally proves
 
T

Tim Roberts

Scott David Daniels said:
What kind of shenanigans must a parser go through to translate:
<x**2 with(x)><<x**3 with(x)>

this is the comparison of two functions, but it looks like a left-
shift on a function until the second with is encountered. Then
you need to backtrack to the shift and convert it to a pair of
less-thans before you can successfully translate it.

C++ solves this exact problem quite reasonably by having a greedy
tokenizer. Thus, that would always be a left shift operator. To make it
less than and a function, insert a space:
<x**2 with(x)>< <x**3 with(x)>
 
P

Paul Rubin

James Richards said:
Personally, I can't recall any decent programmer I know who objects
to actually writing out a variable name. In fact, I don't know a
single "real" programmer (this is one who writes programs he intends
to look at again in, say, 3 weeks) who doesn't insist on writing
"real" variable names.

The issue is whether you want to name every intermediate result in
every expression.

sum = a + b + c + d + e

is a lot nicer than

x1 = a + b
x2 = c + d
x3 = x1 + e
sum = x2 + x3

the language has nicely kept all those intermediate results anonymous.

Python has first-class functions, which, like recursion, is a powerful
idea that takes some getting used to. They let you say things like

def derivative(f, t, h=.00001): # evaluate f'(t) numerically
return (f(t+h) - f(t)) / h

dy_dt = derivative(cos, 0.3) # approx. -sin(0.3)

With anonymous functions, you can also say:

dy_dt = derivative(lambda x: sin(x)+cos(x), 0.3) # approx. cos(.3)-sin(.3)

Most Python users have experience with recursion before they start
using Python, so they don't see a need for extra keywords to express
it. Those not used to first-class functions (and maybe some others)
seem to prefer extra baggage. For many of those used to writing in
the above style, though, there's nothing confusing about using a
lambda there instead of spewing extra verbiage to store that
(lambda x: sin(x)+cos(x)) function in a named variable before
passing it to another function.
 
K

Kay Schluehr

Tim said:
C++ solves this exact problem quite reasonably by having a greedy
tokenizer. Thus, that would always be a left shift operator. To make it
less than and a function, insert a space:
<x**2 with(x)>< <x**3 with(x)>

Python does have such a greedy/longest match tokenizer too:
-> Exception

Kay
 
D

D H

Mike said:
Rewriting a canonical abuse of lambda in this idiom gives:

myfunc = def @(*args):
return sum(x + 1 for x in args)

Nice proposal. Technically you don't need the @ there, it is
superfluous. But then again so is the colon, so whatever floats your boat.

class Spam(object):
myprop = property(fget = def @(self):
return self._properties['myprop']
,
fset = def @(self, value):
self._properties['myprop'] = value
,
fdel = def @(self)
del self._properties['myprop']
,
doc = "Just an example")

I think the anonymous lambdas need to be outside the parentheses to be
parsable. Maybe like this:

class Spam(object):
myprop = property(fget, fset, fdel, doc="just an example"):
where fget = def (self):
.........
where fset = def (self):
.........
where fdel = def (self):
...........

As you can see, it doesn't save much over the traditional way since you
have to name the "anonymous" lambdas anyway.
 
P

Paul Rubin

D H said:
where fdel = def (self):
...........
As you can see, it doesn't save much over the traditional way since
you have to name the "anonymous" lambdas anyway.

It saves polluting the surrounding namespace with superfluous variables
that aren't going to be used again.
 
P

Paolino

why (x**2 with(x))<(x**3 with(x)) is not taken in consideration?

If 'with' must be there (and substitue 'lambda:') then at least the
syntax is clear.IMO Ruby syntax is also clear.





___________________________________
Yahoo! Mail: gratis 1GB per i messaggi e allegati da 10MB
http://mail.yahoo.it
 
P

Paddy

Christopher Subich said:
Basically, I'd rewrite the Python grammar such that:
lambda_form ::= "<" expression "with" parameter_list ">"

I do prefer my parameter list to come before the expression. It would
remain consistant with simple function definitions.

- Cheers, Paddy.
 
P

Paddy

Christopher Subich said:
Basically, I'd rewrite the Python grammar such that:
lambda_form ::= "<" expression "with" parameter_list ">"

I do prefer my parameter list to come before the expression. It would
remain consistant with simple function definitions.

- Cheers, Paddy.
 
S

Stefan Rank

why (x**2 with(x))<(x**3 with(x)) is not taken in consideration?

If 'with' must be there (and substitue 'lambda:') then at least the
syntax is clear.IMO Ruby syntax is also clear.

I am sorry if this has already been proposed (I am sure it has).

Why not substitue python-lambdas with degenerated generator expressions::

(lambda x: func(x)) == (func(x) for x)

i.e. a one time callable generator expression (missing the `in` part).
The arguments get passed into the generator, I am sure that can be
combined with the PEP about passing args and Exceptions into a generator.
 
P

Paul Rubin

Stefan Rank said:
I am sorry if this has already been proposed (I am sure it has).
Why not substitue python-lambdas with degenerated generator expressions::

(lambda x: func(x)) == (func(x) for x)

I don't think I've seen that one before, and FWIW it's kind of cute.

But what's wrong with leaving lambdas the way they are? Anyone who can
understand generator expressions can understand lambda. I don't see any
point in trying to improve on lambda, without actually fixing its main
problem, which is the inability to create multi-statement closures.
 
K

Kay Schluehr

Mike said:
I know, lambda bashing (and defending) in the group is one of the most
popular ways to avoid writing code. However, while staring at some Oz
code, I noticed a feature that would seem to make both groups happy -
if we can figure out how to avoid the ugly syntax.

This proposal does away with the well-known/obscure "lambda"
keyword. It gives those who want a more functional lambda what they
want. It doesn't add any new keywords. It doesn't add any new magic
characters, though it does add meaning to an existing one. That could
be replaced by a new magic token, or adding magic meaning to a
non-magic token. It breaks no old code either way.

Mike,

in modern functional language design one starts with certain lambda
expressions and add syntax sugar ( operational / statement syntax ) for
constructs based on them. In Python statement- and expression syntax
were introduced seperately. Pythons lambda is simply the symptom of
this separation. Now you suggest ( not the first time on this list or
at python-dev ) to put statements into expressions leading to a strange
syntax and conflicting with Pythons indentation rules.

Another way to deal with the restrictions of lambda is going the other
way round and simply propose expression syntax for conds and
assignments.

Using guards '||' and the keyword 'then' for conditional expressions:

( || x>=0 then f(x) || True then f(-x) )

Or shorter dropping 'then' in the second condition:

( || x>=0 then f(x) || f(-x) )

Both translates to:

if x>=0:
f(x)
else:
f(-x)

Using a reverse arrow for assignments:

x <- y

For loops can be replaced by functional constructs ( use map() or a
list/generator comprehension ).

Finally the lambda keyword can be replaced by expressional syntax e.g.
( EXPR from ARGS ):

Examples:
f = ( || x>=0 then f(x) || True then f(-x) from (x,) )
g = ( || x< 0 then self._a <-x || self._a <- 0 from (x,))

Kay
 
P

Paul Rubin

Kay Schluehr said:
Examples:
f = ( || x>=0 then f(x) || True then f(-x) from (x,) )
g = ( || x< 0 then self._a <-x || self._a <- 0 from (x,))

Is this an actual language? It looks sort of like CSP. Python
with native parallelism, mmmmm.
 
K

Kay Schluehr

Paul said:
Is this an actual language? It looks sort of like CSP. Python
with native parallelism, mmmmm.

The syntax was inspired by OCamls pattern matching syntax:

match object with
pattern1 -> result1
| pattern2 -> result2
| pattern3 -> result3
...

But for Python we have to look for an expression not a statement
syntax. In order to prevent ambiguities I used a double bar '||'
instead of a single one.

Kay
 
R

Reinhold Birkenfeld

Stefan said:
I am sorry if this has already been proposed (I am sure it has).

Why not substitue python-lambdas with degenerated generator expressions::

(lambda x: func(x)) == (func(x) for x)

i.e. a one time callable generator expression (missing the `in` part).
The arguments get passed into the generator, I am sure that can be
combined with the PEP about passing args and Exceptions into a generator.

It's hard to spot, and it's too different to a genexp to have such a similar
syntax.

Reinhold
 
S

Seth Nielson

I understand that there are a number of people who wish to remove
lambda entirely from the language. Nevertheless, I find it a useful
and powerful tool in actual development.

Any replacement must support the following: *delayed evaluation*.

I need a convenient (def is not always convenient) way of saying,
"don't do this now". That is why I use lambda.

-- Seth Nielson
 

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,774
Messages
2,569,596
Members
45,142
Latest member
arinsharma
Top