Ruby and Python

Alex Martelli aleaxit at yahoo.com
Sat Nov 18 18:32:25 EST 2000


"graham" <graham73 at telocity.com> wrote in message
news:B63C5C89.172D8%graham73 at telocity.com...
    [snip]
> But functions in Python aren't first class either. The most obvious
evidence
> of this is having to write manual closures.

What does this have to do with functions' "first-classness"?  Python
doesn't do nesting of lexical scopes -- which is a completely separate
issue from that.  A function-object in Python includes two references
to namespaces -- a 'local' one and a 'global' one -- and each such
namespace carries all the bindings it wants, without delegation
to other namespaces [well, the global namespace does such a
delegation, to the builtins' namespace; but the local one does
no delegation].

If anything, one might say that _namespaces_ aren't first-class
objects -- although that's not exactly the point: it's the _namespace
reference slots_ that aren't "first-class reference slots", in a way...
they're NOT able to hold references to just any object satisfying
a mapping's interface; rather, they can only refer to very specific
kinds of things.  (Another peculiar kind of reference-slots are
those in class-objects: if set to refer to a function, they 'mutate'
that by wrapping it up as an unbound-method).  There are many
other cases of reference-slots that are limited in some ways re
the kinds of things they can refer to; for example, the keys in a
dictionary must refer to immutable-objects; but these can most
often be framed in terms of 'satisfying a certain interface' -- e.g.,
in the case of dictionary-keys, "implementing __hash__" can
stand for "being immutable".  This is not the case for namespaces.

But that is by the by.  A function-object can be generated in
several ways, with different abilities and limitations (def, which
has the limitation of making you give it a name, and lambda,
which has the limitation of only allowing an expression as
the codeobject, 'automatically pick up' the current globals and
also build the codeobject that the function-object wraps;
new.function, instead, allows [and requires] you to specify
codeobject and globals explicitly).

(Similarly, for other objects, such as, say, strings; you can
generate them in several different ways -- as literals, or by
applying the builtin function str, or by calling the .join
method on another string object, etc).

Once you have generated (or otherwise obtained a reference to)
an object (be it a function, or a string) you can compare it
with other, store references to it in sundry places (including
passing it as an argument, binding to it a variable or an
object's attribute, etc), examine its attributes (have them
listed with dir, check for them with hasattr, fetch them with
getattr or with direct dot-syntax, etc), apply appropriate
operations (e.g., for a string, like some other objects but
not all, such operations include '+' but not 'calling'; for a
function, like some other objects but not all, they include
calling but not '+').

I.e., the objects are "first-class citizens": no special
restrictions apply to the ways you can handle them.


> How does this differ from
> wrapping a function in a callable object (which you can do in Python too).

A function *is* a callable object (which wraps, among other
things, a code-object, which is not callable).  Of course, like
any other callable (or other object), it can be further 'wrapped'
(to any 'depth' you desire), but that does not in the least
affect its 'first-classness'.

The details of how you can (if you wish) 'prepare' various
sub-objects that the function-object wraps are a further,
and again completely different, issue.  You may dislike the
lack of nested-lexical-scoping (which would allow one more
way to 'prime' the function's namespace), but in the end
it's not much more than an issue of syntax -- no wrapping
of function objects being involved.

E.g., consider a classical example:

>>> import new
>>> def adder(n):
            def add(x):
                return x+n
            return new.function(add.func_code,vars())
>>> x=adder(5)
>>> x(20)
25
>>> y=adder(7)
>>> x(20)
25
>>> y(20)
27

You may dislike having to be explicit about the details
of the new function object -- 'use the code of local
function add, use the current variable look-up settings
as the "global" namespace' -- and wish new.function
defaulted them for you, but it doesn't.  Big deal.


Alternatively (and as it happens more idiomatically),
the 'currying (from the right...)' idea...:

>>> def adder(n):
            def add(x,n=n):
                return x+n
            return add

or equivalently

>>> def adder(n):
            return lambda x,n=n: x+n

produce quite similar effects by other means (telling
the codeobject to look for n in the local dictionary,
and 'priming' that dictionary, rather than using the
new.function feature of specifying the _global_ one).

In none of these cases is the function object 'wrapped'
into anything (it _does_ 'wrap' references to a code
object and the two namespaces, as always), and no
matter which route you take to build/obtain it, you can
use it much like anything else. I.e., it's first-class...


Alex






More information about the Python-list mailing list