[Currying and partial application](http://www.2ality.com/2011/09/currying-vs-part-eval.html) are two ways of transforming a function into another function with a generally smaller arity. While they are often confused with each other, they work differently. The goal is to learn to differentiate them.

## Currying

> Is the technique of transforming a function that takes multiple arguments in such a way that it can be called as a chain of functions each with a single argument.

Currying takes a function:

```
f: X × Y → R
```

and turns it into a function:

```
f': X → (Y → R)
```

Instead of calling `f` with two arguments, we invoke `f'` with the first argument. The result is a function that we then call with the second argument to produce the result. Thus, if the uncurried `f` is invoked as:

```
f(3, 5)
```

then the curried `f'` is invoked as:

`f'(3)(5)`

### Example

Given this function:

```python
def add(x, y, z):
  return x + y + z
```

We can call in a normal way:

```python
add(1, 2, 3) # => 6
```

But we can create a curried version of `add(a, b, c)`function:

```python
curriedAdd = lambda a: (lambda b: (lambda c: add(a,b,c)))
curriedAdd(1)(2)(3) # => 6
```

## Partial application

> Is the process of fixing a number of arguments to a function, producing another function of smaller arity.

Partial application takes a function:

```
f: X × Y → R
```

and a fixed value `x` for the first argument to produce a new function

```
f': Y → R
```

`f'` does the same as `f`, but only has to fill in the second parameter which is why its arity is one less than the arity of `f`. One says that the first argument is bound to `x`.

### Example

```python
partialAdd = lambda a: (lambda *args: add(a,*args))
partialAdd(1)(2, 3) # => 6
```

-------------

Your work is to implement a generic `curryPartial()` function allows either currying or partial application.

For example:

```python
curriedAdd = curryPartial(add)
curriedAdd(1)(2)(3) # => 6

partialAdd = curryPartial(add, 1)
partialAdd(2, 3) # => 6
```

We want the function be very flexible.

All these examples should produce the same result:

```python
curryPartial(add)(1)(2)(3) # =>6 
curryPartial(add, 1)(2)(3) # =>6 
curryPartial(add, 1)(2, 3) # =>6 
curryPartial(add, 1, 2)(3) # =>6 
curryPartial(add, 1, 2, 3) # =>6 
curryPartial(add)(1, 2, 3) # =>6 
curryPartial(add)(1, 2)(3) # =>6 
curryPartial(add)()(1, 2, 3) # =>6 
curryPartial(add)()(1)()()(2)(3) # =>6 

curryPartial(add)()(1)()()(2)(3, 4, 5, 6) # =>6 
curryPartial(add, 1)(2, 3, 4, 5) # =>6 
```

And also all of these:

```python
curryPartial(curryPartial(curryPartial(add, 1), 2), 3) # =>6
curryPartial(curryPartial(add, 1, 2), 3) # =>6
curryPartial(curryPartial(add, 1), 2, 3) # =>6
curryPartial(curryPartial(add, 1), 2)(3) # =>6
curryPartial(curryPartial(add, 1)(2), 3) # =>6
curryPartial(curryPartial(curryPartial(add, 1)), 2, 3) # =>6
```

Difficulty: interview

Starter code:

def curry_partial(f,*args):
	

Solutions:
class CurryPartial:
    def __init__(self, func, *args):
        self.func = func
        self.args = args
    
    def __call__(self, *args):
        return CurryPartial(self.func, *(self.args + args))
    
    def __eq__(self, other):
        try:
            return self.func(*self.args) == other
        except TypeError:
            return CurryPartial(self.func, *self.args[:-1]) == other

def curry_partial(f,*initial_args):
  "Curries and partially applies the initial arguments to the function"
  return CurryPartial(f, *initial_args)

def curry_partial(f, *args):
    if not callable(f): return f # ignore extra args
    try: return f(*args) # handle nested currying
    except: pass
    try: return f() # ignore extra args if we are done
    except: pass
    if args: return curry_partial(lambda *a: f(args[0], *a), *args[1:]) # curry
    else: return lambda *a: curry_partial(f, *a) # leave to caller


import inspect
def curry_partial(f,*args):
    if not callable(f) : return f
    req = list(inspect.signature(f).parameters)
    return f(*args) if req and req[0]=='_e' else f(*args[:len(req)]) if len(req) <= len(args) else \
           lambda *_e:curry_partial(f, *args + _e)

def curry_partial(f,*initial_args):
  if not callable(f):
      return f
  exp_n_args = f.__code__.co_argcount
  given_n_args = len(initial_args)
  if exp_n_args - given_n_args > 0:
      # a lambda function that has a variable list of parameters
      # when called, it adds these parameters to the already given parameters
      # and curries the function with the combined parameters
      # In case the number of parameters is enough after that, the function will be called
      # if not, it is curried again recursively
      return lambda *args: curry_partial(f, *(initial_args + args))
  if exp_n_args == 0:
      # f is the lambda returned by curry_partial and has a variable argument list
      return f(*initial_args)
  else:
      # f is a function with a fixed number of arguments, so give it exactly how much it wants
      return f(*initial_args[:exp_n_args])

def curry_partial(f,*args):
    if not callable(f):
        return f
    if len(args) < f.__code__.co_argcount:
        return lambda *a: curry_partial(f, *(args + a))
    return f(*args[:f.__code__.co_argcount or None])

def curry_partial(fn, *x):
    if not callable(fn): return fn        
    size = fn.__code__.co_argcount or None
    return fn(*x[:size]) if size <= len(x) else lambda *y: curry_partial(fn, *(x+y))

def curry_partial(f,*initial_args):
    def f1(*args):
        def f2(*args2):

            if not callable(f):                             return f     #we are done already
            elif f.__code__.co_argcount == 0:               return f(*(args+args2))# nested call
            elif f.__code__.co_argcount > len(args+args2):  return f1(*(args+args2))#not enough args
            else:                                           return f(*(args+args2)[0:f.__code__.co_argcount])#victory

        return f2
    return f1(*initial_args)()

from inspect import getfullargspec

def curry_partial(f, *initial_args, arg_spec=None): # I don't think this is what I needed to do...
    if not arg_spec:
        if not callable(f):
            return f
        arg_spec = getfullargspec(f)
    param_l = len(arg_spec.args)
    if param_l < len(initial_args) + 1:
        if arg_spec.varargs:
            return f(*initial_args)
        else:
            return f(*initial_args[:param_l])
    else:
        def fun(*args):
            return curry_partial(f, *(initial_args + args), arg_spec=arg_spec)
        return fun