Search code examples
pythonfunctional-programmingcurryingpartials

Can we use keyword arguments and curry until all arguments are received?


Can we use keyword arguments and curry a function until all arguments are received in any order?

For example I have this code:

def create_folder_transformer(folder):
    return lambda predicate: lambda file_transformer: map(file_transformer,
        filter(predicate,
            os.listdir(folder)))

I can create a folder_transformer with a specific folder and then mention the`predicate etc. But it has a specific order. However, I don't think it need to be tied to that order.

I would like to achieve something like this instead:

  1. If I pass predicate, I get a partial function that takes folder and file_transformer as arguments. Now if I supply folder, I get a partial function that takes file_transformer.
  2. If I pass file_transformer, I get a partial function that takes predicate and folder as arguments. Now if I supply predicate, I get a partial function that takes folder.

In short, is there an inbuilt partial creator that recursively keeps on generating partial functions until all inputs are obtained; if inputs are satisfied just execute the code. I believe it is called currying in Haskell and it is how functions perform by default.

Use cases where I think it might help:

  1. When I am transforming a specific folder with n operations, creating a partial with folder will be better.

  2. When I am having a specific predicate like - filter out mp4 files across many folders - a partial with a predicate like string.endswith(".mp4") will be better.


I read the partial docs but these partials don't return partials if I haven't filled in some args. But since I have declared it as None, I can't expect it either. Ideally, I would like my function itself to behave like that without even worrying about using an additional function like partial i.e it should be baked into my function.

def folder_transformer(folder=None, predicate=None, transformer=None):
    return map(transformer, filter(predicate, os.listdir(folder)))

file_transformer = partial(folder_transformer, predicate=os.path.isfile)

# This gives me a "map" object, but I want another partial takes "transformer".
current_transformer = file_transformer(folder=folder)
# This works, but my question is can I make my function do this automatically. 
current_transformer = partial(file_transformer,folder=folder)

Solution

  • I came with a small script for my personal use.

    PS: If anyone knows a library that can do something similar, please let me know.

    import inspect
    
    def curry(function):
        original_func = function
        original_args = inspect.signature(function).parameters
    
        def wrapper(**kwargs):
            call = True
            current_args = {}
            for each in original_args:
                if kwargs.get(each):
                    current_args[each] = kwargs.get(each)
                else:
                    call = False
            if call:
                original_func(**current_args)
            else:
                def partial(**nargs):
                    current_args.update(nargs)
                    return wrapper(**current_args)
                return partial
    
        return wrapper
    
    @curry
    def foo(a=None, b=None, c=None):
        print(a, b, c)
    
    # first partial
    bar_with_a = bar(a=1)
    # next partial
    bar_with_a_and_b = bar_with_a(b=1)
    # next partial
    bar_with_a_and_b = bar_with_a_and_b(b=2)
    # next partial
    bar_with_a_and_b = bar_with_a_and_b(a=2)
    # call
    bar_with_a_and_b(c=2)