Is there a name for the function that returns a positionally-expanding version of its argument?
-
12-11-2019 - |
Question
Consider splatter
in this Python code:
def splatter(fn):
return lambda (args): fn(*args)
def add(a, b):
return a + b
list1 = [1, 2, 3]
list2 = [4, 5, 6]
print map(splatter(add), zip(list1, list2))
Mapping an n-ary function over n zipped sequences seems like a common enough operation that there might be a name for this already, but I have no idea where I'd find that. It vaguely evokes currying, and it seems like there are probably other related argument-centric HOFs that I've never heard of. Does anyone know if this is a "well-known" function? When discussing it I am currently stuck with the type of awkward language used in the question title.
Edit
Wow, Python's map
does this automatically. You can write:
map(add, list1, list2)
And it will do the right thing, saving you the trouble of splatter
ing your function. The only difference is that zip
returns a list whose length is the the length of its shortest argument, whereas map
extends shorter lists with None
.
Solution 2
I randomly saw this in my list of "Questions asked," and was surprised that I now know the answer.
There are two interpretations of the function that I asked.
The first was my intent: to take a function that takes a fixed number of arguments and convert it into a function that takes those arguments as a fixed-size list or tuple. In Haskell, the function that does this operation is called uncurry
.
uncurry :: (a -> b -> c) -> ((a, b) -> c)
(Extra parens for clarity.)
It's easy to imagine extending this to functions of more than two arguments, though it can't be expressed in Haskell. But uncurry3
, uncurry4
, etc. would not be out of place.
So I was right that it "vaguely evokes currying," as it is really the opposite.
The second interpretation is to take a function that takes an intentionally variable number of arguments and return a function that takes a single list.
Because splat
is so weird as a syntactic construct in Python, this is hard to reason about.
But if we imagine, say, JavaScript, which has a first-class named function for "splatting:"
varFn.apply(null, args)
var splatter = function(f) {
return function(arg) {
return f.apply(null, arg);
};
};
Then we could rephrase that as merely a partial application of the "apply
" function:
var splatter = function(f) {
return Function.prototype.apply.bind(f, null);
};
Or using, Underscore's partial
, we can come up with the point-free definition:
var splatter = _.partial(Function.prototype.bind.bind(Function.prototype.apply), _, null)
Yes, that is a nightmare.
(The alternative to _.partial
requires defining some sort of swap
helper and would come out even less readable, I think.)
So I think that the name of this operation is just "a partial application of apply
", or in the Python case it's almost like a section of the splat operator -- if splat were an "actual" operator.
But the particular combination of uncurry
, zip
, and map
in the original question is exactly zipWith
, as chris pointed out. In fact, HLint by default includes a rule to replace this complex construct with a single call to zipWith
.
I hope that clears things up, past Ian.
OTHER TIPS
I think zipWith
is the function that you are searching (this name is at least used in Haskell). It is even a bit more general. In Haskell zipWith
is defined as follows (where the first line is just the type):
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
zipWith _ _ _ = []
And your example would be something like
zipWith (+) [1, 2, 3] [4, 5, 6]
Since I do not know python very well I can only point to "zipWith analogue in Python?".