[Tutor] Thoughts on little lambda

Kirby Urner urnerk@qwest.net
Mon, 11 Mar 2002 10:37:16 -0800


>
>Python 2.2's IDLE should resolve the problem.  Good luck!

2.2 does make import from __future__ work directly in the
IDLE shell (you needn't be in a module), e.g.:

  >>> from __future__ import division
  >>> 3/2
  1.5

However, in this particular case, using the nested scopes
feature, the "fix" comes from not needing to import from
__future__ at all, because nested scopes are the default
for version 2.2 on.

Just to review the construct, I was suggesting a way to
make "function factories" (functions that build other
functions) which have internal complexity beyond lambda's
ability by itself, is to use an internal function called
with lambda.

E.g. the example below includes a print statement --
something lambda can't, by itself, invoke:

  >>> def makeadder(n):
          def main(x):
             print "Adding %s" % n
             return x + n
          return lambda x: main(x)

This is the classic case of building an "adder" e.g.:

  >>> add1 = makeadder(1)
  >>> add1(5)
  Adding 1
  6

Now it comes up in many applications that you want to
do one thing to a number if it's odd, something else
if it's even.  The template below accepts two *functions*
as inputs, and builds a "switcher" with 'em:

  >>> def evenodd(fe,fo):
         def main(x):
            if x%2:  # odd
               return fo(x)
            else:    # even
               return fe(x)
         return lambda x: main(x)

So, for example, if you'd earlier defined add0 and add1
using the makeadder() function, then here you could pass
these to function-builder evenodd() and get a function
which adds 0 to evens, 1 to odds:

  >>> switcher = evenodd(add0,add1)
  >>> switcher(3)
  Adding 1
  4
  >>> switcher(2)
  Adding 0
  2

But note that evenodd(fe,fo) is designed to accept *any*
two functions, one to execute if the input is even,
the other if odd.

I think this provides a good example of "functional
programming" in the sense of using functions to build
other functions, which in turn involves thinking of
functions as the arguments of other functions.

But my main focus was to explore ways of overcoming
little lambda's inherent limitations, by taking advantage
of nested scopes to put the real guts of a function in
some internally called construct.  The arguments to
the builder provide the static content for these guts
(variable only at the time the function is built), and
then the final lambda return statement handles what
will be the dynamic inputs to the built function.

In the case of a polynomial builder, you could have a
list of coefficients at "build time", then, presuming
it's a poly of a single variable (but it doesn't *have*
to be) pass "x" in the lambda statement:

  >>> def buildpoly(coeffs):
         def main(x):
            dim = len(coeffs)-1
            sum = 0
            for c in coeffs:
               sum += c * pow(x,dim)
               dim -= 1
            return sum
         return lambda x: main(x)

  >>> p = buildpoly([2,3,1])    # f(x) = 2x^2 + 3x + 1
  >>> p(3)
  28
  >>> 2*(3**2) + 3*(3) + 1
  >>> q = buildpoly([1,2,3,4])  # f(x) = x^3 + 2x^2 + 3x + 4
  >>> q(10)
  1234
  >>> 10**3 + 2*(10**2) + 3*10 + 4
  1234

The important thing to note here is buildpoly is
returning callable functions, which functions are then
applied to specific numbers.

Note also that the internally called construct might be
recursive.  In the example below, recursop(op) wants an
operation as input.  It builds a recursive function
which decrements the argument by 1 each time, stopping
when the argument reaches 1 (up to the user here not to
pass 1.5 or -1 -- asking for trouble [1]).

So if you import mul and add from operator, you can
use recursop to build a primitive factorial and and
triangular number cruncher.  I call it "triangular"
because 5+4+3+2+1 may be represented as a triangle:

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

  >>> def recursop(op):
          def main(x):
             if x==1: return x
             else:    return apply(op,(x,main(x-1)))
          return lambda x: main(x)

  >>> factorial = recursop(mul)
  >>> factorial(5)
  120
  >>> factorial(7)
  5040
  >>> 7*6*5*4*3*2
  5040

  >>> triangular = recursop(add)
  >>> triangular(10)
  55
  >>> 10+9+8+7+6+5+4+3+2+1
  55

It's a little harder to figure what's going on with
recursive subtraction, but it works, if you import sub:

  >>> something = recursop(sub)
  >>> something(10)
  5

i.e.:

  >>> 10-(9-(8-(7-(6-(5-(4-(3-(2-1))))))))
  5

These ideas are not new to programmers.  It's the kind
of stuff you see in Scheme/LISP all the time.  I was
just wanting to see how Python's little lambda (vs. the
big honker lambda of the LISPs) isn't really a major
limitation when it comes to writing function-building
functions.

The new way of doing scoping is what adds power, as the
bindings are all local within the builder, and the lambda
is charged only with passing what will be the external
arguments to the built output functions -- expecting lots
of reuse.  The builder's calling args, on the other hand,
are the one-time definers of the product's behavior.

A natural next topic would be to explore this same
construct as a way of returning generators, i.e. functions
built around the new 'yield' keyword.

Kirby

[1] because we're using an internal function to
provide the guts, more elaborate argument checking,
including try/excepts, are certainly doable.