alancalvitti
alancalvitti

Reputation: 476

How to curry a function at a given argument position

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

Answers (2)

Mulan
Mulan

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

developer_hatch
developer_hatch

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

Related Questions