Steven said:
Fascinating. A couple of thoughts:
- I really wish that people would make up their minds about what currying
is, and how it differs from partials and closures. If the people who
do know can't agree, how do they expect the rest of us to understand?
Me too, but I disagree with the people who won the argument on the name.
Curry derives its name from the mathematician who formulated the
Curry-Howard isomorphism that says a function of multiple arguments
can be expressed as a function taking a single argument which returns a
function which takes a single argument which... takes a single argument
and returns the result of the original multi-argument function. This
isomorphism means that you need only define functions which take single
arguments. In some functional languages, functions are called adjacent
to their arguments, and a function which looks like it takes multiple
arguments is actually taking a single argument of a tuple.
The Curry recipe I put in the Python Cookbook did the analogous thing
for the Python language, since there is no way for a "Curry" argument
to know when to call the underlying function, rather than simply
accumulate arguments.
- What, if anything, is the difference between a closure and an iterator?
Is an iterator just a closure wrapped up with an API?
A closure is a function and the environment it was created in.
So, for example, the result of calling:
def multiplier(n):
def result(m):
return n * m
multiplier(2) returns a closure (the function called result and its
environment where n is set to 2).
An iterator is a different beasty.
- The first sample code seems needlessly confusing to me. It gives:
class partial(object):
def __init__(*args, **kw):
self = args[0]
self.fn, self.args, self.kw = (args[1], args[2:], kw)
def __call__(self, *args, **kw):
if kw and self.kw:
d = self.kw.copy()
d.update(kw)
else:
d = kw or self.kw
return self.fn(*(self.args + args), **d)
It seems to me to needlessly break the convention that the first argument
is self.
Actually, it does, but only because the second doesn't break it as well.
> to no benefit and considerable reduction in clarity. After some
experimentation, I worked out what it was doing, and realised that it
would work just as well but much less obscurely if the __init__ function
was written as:
def __init__(self, fn, *args, **kw):
self.fn, self.args, self.kw = fn, args, kw
It would pretty much work like this, _but_ you could not used named args
of self or fn when using one of these things.
A better definition:
class partial(object):
def __init__(*args, **kw):
self = args[0]
self.fn, self.args, self.kw = (args[1], args[2:], kw)
def __call__(*args, **kw):
self = args[0]
if kw and self.kw:
d = self.kw.copy()
d.update(kw)
else:
d = kw or self.kw
return self.fn(*(self.args + args[1:]), **d)
Now you can, for example:
def function(self, other, fn):
return fn([self, other])
defaults = partial(function, self=object(), fn=repr)
print defaults(other=3), defaults(self='self', other=3)
The fancy-schmancy stuff is to keep from hiding some names. I suspect
both args and kw have this problem as well. It might have been
reasonable to call them __self, __function, __args, and __kwargs
and do it your way.
- It seems a shame to me that having created a partial _function_ using
that technique, type(partial(...)) returns <class partial>. It would be
nicer if the type made it more obvious that the instance was callable.
But callable(defaults) returns True. You will eventually learn that
partial is callable, just as you know "type"s are callable. Seeing
that it is class partial, you can (if you are nosey), see what the
arguments already provided are (so you could get a nicer print).
For example, adding a method to partial:
def __repr__(self):
return 'partial(%r, *%r, **%r)' % (self.fn, self.args, self.kw)