Lazy argument evaluation (was Re: expression form of one-to-many dict?)

Nick Coghlan ncoghlan at iinet.net.au
Tue Dec 21 08:44:18 EST 2004


Mike Meyer wrote:
> 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[i]
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.

-- 
Nick Coghlan   |   ncoghlan at email.com   |   Brisbane, Australia
---------------------------------------------------------------
             http://boredomandlaziness.skystorm.net



More information about the Python-list mailing list