…the world might be a bit of a better place, but not really.
“But Quad, Python does have lambdas. Look!”
>>> double = lambda x: x+x >>> double(5) 10 >>> quadruple = lambda x: double(double(x)) >>> quadruple(5) 20
I tell you, they’re lies.
SICP taught us message passing. Here’s a variation of an example they used.
>>> def complex_number(a, b): ... return (lambda msg: ... if msg == "real part": File "<stdin>", line 3 if msg == "real part": ^ SyntaxError: invalid syntax
BZZT! No can do. Lambdas can’t contain keywords/control flow (well, ternary
if). Lambdas contain expressions. Instead, we have to give it a name.
def complex_number(a, b): def dispatch(msg): if msg == "real": return a elif msg == "imag": return b else: raise Exception("Invalid message.") return dispatch
[Edit 4/4/12: To make this less trivial, instead of returning just
a, have it print it additionally.]
And with that, we can do the following.
>>> z = complex_number(1, 2) >>> z("real") 1 >>> z("imag") 2 >>> z("complex") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 8, in dispatch Exception: Invalid message.
In Scheme or Lisp, we don’t need this extra naming baggage. We can make lambdas do arbitrary things. The equivalent of the Python definition using lambdas would be:
(define (complex-number a b) (lambda (msg) (cond ((eq? msg 'real) a) ((eq? msg 'imag) b) (else (error "Invalid message.")))))
And with this, we have:
> (define z (complex-number 1 2)) > (z 'real) 1 > (z 'imag) 2 > (z 'magic) error: Invalid message.
In the Scheme code, we could literally return a lambda function itself. In Python, if we want the full power of functions, we need to give them a name.
This reminds me of the C era of programming. In order to provide a callback to, say, some GUI action, we needed to make a function (with a name). But of course, functions in Python have an advantage: they can be created in many places (but not everywhere! keep reading.) and they can grab data from the environment in which they’re created (closures).
So, if we can
def stuff in most places in Python code, then we can replace lambdas somewhat easily. A “true” lambda with unrestricted usage,
lambda NAME(ARGS): BODY
could be translated into
def NAME(ARGS): BODY NAME
If only we could do that…
In Common Lisp, we have something like
def that works for internal definitions (definitions inside another function definition). The mechanism is called
labels. The translation of the
complex_number function in Python to Lisp is this.
(defun complex-number (a b) (labels ((dispatch (msg) (cond ((string-equal msg "real") a) ((string-equal msg "imag") b) (t (error "Invalid message."))))) ; end labels defs #'dispatch))
This is essentially the same.
Now, suppose Lisp had lambdas that were as bad as Python’s, but
labels worked just as well as Python’s
Behold, the power of macros!
(defmacro %lambda ((&rest ARGS) &body BODY) (let ((NAME (gensym))) `(labels ((,NAME (,@ARGS) ,@BODY)) #',NAME)))
Look ma, a lambda maker!
The way this works is this. We generate a symbol using
gensym and give it to
NAME (Scheme doesn’t need to do this, since macros are hygienic). Then we fill in the “structure” which is prepended with a backquote. The places we fill things in are prefixed with a comma (or a comma-at, which splices in longer bits of code, like the body of a function). In a sense, we are just filling a template out.
Let me prove to you it works!
First define the function-returning-function:
(defun complex-number (a b) (%lambda (msg) (cond ((string-equal msg "real") a) ((string-equal msg "imag") b) (t (error "Invalid message.")))))
And then try it.
CL-USER> (defvar z (complex-number 1 2)) Z CL-USER> (funcall z "real") 1 CL-USER> (funcall z "imag") 2
(Note: we need that extra
funcall due to the nature of how Common Lisp functions work.)
We can check further stuff, like being able to list lambda functions.
CL-USER> (list (%lambda (x) (+ 2 x)) (%lambda (p) (* 2 p))) (#<function 293DAB6A> #<function 293D22A2>)
Unfortunately, even if Python had macros, this would fail (ergo, Python
defs aren’t really lambdas either, in that they cannot be created anywhere, and aren’t first class in any sense). For example, you cannot make a list of these
defs or return one. Python chose not to have the equivalent of
progn which starts a new “block” that returns its last value. And as a result, we can’t just define a new function anywhere a value could be used. Instead, we have to create the function where a function can be defined, and then put the function value elsewhere. This has the unfortunate effect of leaky, oozing “encapsulation” (kind of like Python’s way of doing classes!). Concretely, to make functions available in a list, one must do [Edit 4/4/12: This was a poor example. The lambdas need to be less trivial. Consider adding a
def funlist(): def f(x): return 2+x def g(p): return 2*p return [f, g]
So, despite people telling me how great Python is, and how you can work around the fact that lambdas aren’t first class, I still wonder, have any of you guys ever read SICP?