import inspect
from minitest import case, tests
from functools import wraps, partial


def makepartial(fn):
    """
    This function is a decorator which allows functions with a fixed
    number of arguments gracefully to be transformed into partial functions
    given too few arguments to execute.
    For instance, if `add(1, 2)` returns 3, `add(1)` should return a function
    `f` such that `f(2)` returns 3.
    """
    
    @wraps(fn)
    def maybepartial(*args, **kwargs):
        """
        Either calls and returns the decorated function, or -- if we don't
        have enough arguments -- creates a partial function with the given
        parameters prefilled.
        """
        sig = inspect.signature(fn)
        if len(args) + len(kwargs) == len(sig.parameters):
            return fn(*args, **kwargs)
        return makepartial(partial(fn, *args, **kwargs))

    return maybepartial


@makepartial
def add(a, b):
    return a + b

# Tests:
@case
def test_correctness(t):
    t.check_equal(add(1, 2), 3)


@case
def test_currying(t):
    t.check_equal(add(1)(2), add(1, 2))
    t.check_equal(add()(1)(2), add(1)(2))

if tests:
    tests.run_all()