Reputation: 476
Is it possible to curry a function at a user-defined n-th position? Functool's partial function can handle partial evaluation of multiple arguments that
from functools import partial
def f(x,y,z):
return [x,y,z]
Then
partial(f,1)(2,3)
[1,2,3]
and
partial(f,1,2)(3)
[1,2,3]
Is it possibly to curry at a given argument position?
curry_at(f,n)
So that
curry_at(f,2)(1,3)(2)
gives
[1,2,3]
where, I think to keep consistent with possible *arg and **kwarg, n
should be no larger than the number of regular arguments?
Is there a method to enumerate the arguments to separate the prefix and postfix arguments to the n-th slot?
Upvotes: 1
Views: 286
Reputation: 135217
It's not common to recommend building functions like this, but in this case, I think it's better to have to have static definitions with a little bit of duplication rather than relying on a dynamic function whose complex behavior is difficult to verify -
def apply_at_1 (f, *a, **kw):
return lambda x, *a2, **kw2: \
f(x, *a, *a2, **kw, **kw2)
def apply_at_2 (f, *a, **kw):
return lambda x, y, *a2, **kw2: \
f(x, y, *a, *a2, **kw, **kw2)
def apply_at_3 (f, *a, **kw):
return lambda x, y, z, *a2, **kw2: \
f(x, y, z, *a, *a2, **kw, **kw2)
def dummy (a, b, c, d, *more):
print (a, b, c, d, *more)
apply_at_1(dummy, 2, 3, 4)(1, 5, 6, 7, 8)
# 1 2 3 4 5 6 7 8
apply_at_2(dummy, 3, 4, 5)(1, 2, 6, 7, 8)
# 1 2 3 4 5 6 7 8
apply_at_3(dummy, 4, 5, 6)(1, 2, 3, 7, 8)
# 1 2 3 4 5 6 7 8
Sure, the dynamic function allows you to apply an argument in any position, but if you need to apply an argument at position four (the 5th argument), maybe it's time to consider a refactor. In this case I think it's better to set a sensible limit and then discipline yourself.
Other functional languages do similar things for typing reasons. Consider Haskell's liftA2
, liftA3
, liftA4
, etc. Or Elm's List.map
, List.map2
, List.map3
, List.map4
, ...
Sometimes it's just easier to KISS.
Here's a version of partial
supporting wild-cards, __
-
def __ ():
return None;
def partial (f, *a):
def combine (a, b):
if not a:
return b
elif a[0] is __:
return (b[0], *combine(a[1:], b[1:]))
else:
return (a[0], *combine(a[1:], b))
return lambda *b: f(*combine(a,b))
def dummy (a, b, c, d, *more):
print (a, b, c, d, *more)
partial(dummy, 1, __, __, 4)(2, 3, 5, 6, 7, 8)
# 1 2 3 4 5 6 7 8
partial(dummy, __, __, 3, __, __, 6, 7, 8)(1, 2, 4, 5)
# 1 2 3 4 5 6 7 8
Upvotes: 1
Reputation: 16224
I tryed a workarond using the index and a for loop, hope it helps:
from functools import partial
def curry_at(funtion,parameter,index,*args):
h = funtion
index-=1
ls = list(args)
ls.insert(index,parameter)
for arg in ls:
h = partial(h,arg)
return h()
a = curry_at(f,'a',2,'c','d')
b = curry_at(f,2,2,1,3)
a
['c', 'a', 'd']
b
[1,2,3]
Upvotes: 0