expression form of one-to-many dict?

N

Nick Coghlan

Mike said:
Personally, I'd love a language feature that let you create a function
that didn't evaluate arguments until they were actually used - lazy
evaluation. That lets you write the C ?: operator as a function, for
a start.

The basic idea is to just specify that those arguments must be zero-argument
callables, and only call them if you actually need them.

The rest of this message is something that just occured to me that could make
that style 'prettier' (if lambda looks as ugly in function calls to you as it
does to me). It's completely untested, though :)

First, some utility functions to convert values and functions with arguments to
a lazily evaluable function:

def lazy(x, *args, **kwds):
"""Executes x(*args, **kwds) when called"""
if args or kwds:
return lambda : x(*args, **kwds)
else:
return x # No arguments, so x must be callable by itself

def lazyval(x):
"""Allows passing a normal value as a deferred argument"""
return lambda : x

def lazycall(x, *args, **kwds):
"""Executes x(*args, **kwds)() when called"""
return lambda : x(*args, **kwds)()


For literals, their constructor provides a zero-argument callable equivalent:

[] -> list
(,) -> tuple
{} -> dict
0 -> int
"" -> str
0L -> long

And the operator module provides a way to delay most operations:

import operator
lazy(operator.mul, x, y) # Delayed x * y
lazy(operator.itemgetter(i), x) # Delayed x
lazy(operator.attrgetter("a"), x) # Delayed x.a
lazycall(lazy(operator.attrgetter("a"), x)) # Delayed x.a()

(That last example is why I added lazycall() to the utility functions)

Then you can write a function called 'select':

def select(selector, *args, **kwds):
if kwds:
return kwds[selector]()
else:
return args[selector]()

And one called 'either' (since 'if' is taken and using 'select' would get the
true case and the false case back to front):

def either(pred, true_case, false_case):
if pred:
return true_case()
else:
return false_case()

And use them as follows:

select(selector, list, lazyval(mylist), lazy(eval, expr, globals(), locals()))
select(selector, c1 = list, c2 = lazyval(mylist), c3 = lazy(myfunc, a, b, c))
either(condition, guarded_operation, lazyval(default_case))

Huh. I think I like the idea of lazy() much better than I like the current PEP
312. There must be something wrong with this idea that I'm missing. . .

Cheers,
Nick.
 
S

Steven Bethard

Nick said:
def lazy(x, *args, **kwds):
"""Executes x(*args, **kwds) when called"""
if args or kwds:
return lambda : x(*args, **kwds)
else:
return x # No arguments, so x must be callable by itself

[snip]

Huh. I think I like the idea of lazy() much better than I like the
current PEP 312.

Well, 'lazy' is definitely much easier to read, reference in the
documentation, etc. than ':' would be.
There must be something wrong with this idea that I'm
missing. . .

Well, it does cost you some conciceness, as your examples show[1]:

lazy(mul, x, y) v.s. :x * y
lazy(itemgetter(i), x) v.s. :x
lazy(attrgetter("a"), x) v.s. :x.a
lazycall(lazy(attrgetter("a"), x)) v.s. :x.a()

Not sure how I feel about this yet. I don't personally need lazy
argument evaluation often enough to be able to decide which syntax would
really be clearer...

Steve

[1] To try to make things as fair as possible, I'm assuming that you've done
from operator import mul, itemgetter, attrgetter
at the top of the module.
 
M

Mike Meyer

Mike Meyer said:
As in, say, calling
x = ternary(c, lambda:t, lambda:f)
? The 'lambda:' is a (not nice-looking, but...) "indication of
non-evaluation"... or am I misundertanding what you're saying?

No, you're saying it exactly right. And that does, indeed, do the
trick. Just a bit ugly is all.

<mike
 
N

Nick Coghlan

Nick said:
def lazycall(x, *args, **kwds):
"""Executes x(*args, **kwds)() when called"""
return lambda : x(*args, **kwds)()

It occurred to me that this should be:

def lazycall(x, *args, **kwds):
"""Executes x()(*args, **kwds) when called"""
return lambda : x()(*args, **kwds)

(Notice where the empty parens are)

Then this does the right thing:

lazycall(lazy(attrgetter('a'), x), y) # x.a(y)

Cheers,
Nick.
 
N

Nick Coghlan

Steven said:
There must be something wrong with this idea that I'm missing. . .


Well, it does cost you some conciceness, as your examples show[1]:

lazy(mul, x, y) v.s. :x * y
lazy(itemgetter(i), x) v.s. :x
lazy(attrgetter("a"), x) v.s. :x.a
lazycall(lazy(attrgetter("a"), x)) v.s. :x.a()

Not sure how I feel about this yet. I don't personally need lazy
argument evaluation often enough to be able to decide which syntax would
really be clearer...


I think you've hit the downside on the head, though. Any time you have to use
the operator module, you take a big hit in verbosity (particularly since noone
ever came up with better names for attrgetter and itemgetter. . .)

There's a reason people like list comprehensions :)

Ah well, I'll let it bake for a while - I'm still not entirely sure about it
myself, since I'm in a similar boat to you w.r.t. lazy evaluation (I usually
just define functions that do what I want and pass them around).

Cheers,
Nick.
 
A

Alex Martelli

Mike Meyer said:
No, you're saying it exactly right. And that does, indeed, do the
trick. Just a bit ugly is all.

Yeah, there's a PEP to provide alternate, less-ugly syntax sugar for
this specific use of lambda, but I don't think it stands any chance of
passing.


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,764
Messages
2,569,565
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top