Code Monkey home page Code Monkey logo

curry.py's Introduction

Note: You probably shouldn't use this. This was created out of curiosity rather than necessity. I can't really imagine any scenarios where functools.partial isn't going to cut it. Having said that, if you find any use cases please let me know!

curry.py

Currying/partial is cool! It helps to make the code we write much DRY-er.

As an example, let's consider a case where we want to increment each value in a sequence by a certain amount.

def increment_by(by, x):
    return x + by

def increment_all(seq, by):
    return map(lambda x: increment_by(by, x), seq)

This is a bit more verbose than it needs to be. Wouldn't it be great if instead we could just write map(increment_by(10), seq)?

@curry
def increment_by(by, x):
    return by + x

def increment_all(seq, by):
    return map(increment_by(by), seq)

This is a really trivial example but it's a lot easier to understand what's going on when we remove the noise of the lambda.

A more interesting example might be dependency injection (is this the right term?) when working with a database:

import database

@curry
def query(connection, query_string):
    return connection.query(query_string)

def main():
    query = query(database)
    print(query('select * from users'))

How does it work?

Essentially, we collect arguments until a certain number is reached, then call the function with the collected arguments.

Initially, I thought this would be possible using a closure. Assuming we have a way of knowing the number of arguments a function takes (a non-trivial question), we could try something like this (sans kwargs):

def curry(func):
    args = []
    def inner(*new_args):
        args = args + new_args
        if len(args) == get_arg_count(func): # how does get_arg_count work?
            return func(*args)
        return inner
    return inner

Nice and simple. But also horribly wrong. Each call to the curried function will modify the same closure, resulting in this mess:

>>> add = curry(lambda a, b: a + b)
>>> add10 = add(10)
>>> add10(1)
11
>>> add10(2)
<function inner at 0x10a91af28>

Ideally, this would return 12 rather than another function. This happens because args now equals [10, 1, 2], which is not equal to the number of args we want, so the function returns inner again.

We can fix this by re-currying the original function every time inner is called and doesn't have enough args, like this (again, sans support for kwargs to keep things simple):

def curry(func, args=None):
    original_args = args if args else tuple()
    def inner(*new_args):
        next_args = original_args + new_args # no mutation!
        if len(next_args) == get_arg_count(func):
            return func(*next_args)
        return curry(func, args=next_args)
    return inner

(more info to come)

Goals for the project

Features:

  • functions with any number of arguments
  • works with builtin functions
  • pass keyword arguments to the curried function
  • preserve the name of passed functions
  • think of some way to tell the function to be called with the arguments it has so far. This would allow for default arguments to be used. There are two approaches that I can think of:
    • curried(1)(2, done=True) - has the benefit of being more flexible, can use as many of the default arguments as we like but is a bit less elegant
    • @curry(use_default=True) - simpler but less flexible

curry.py's People

Contributors

akspi avatar ben-e-whitney avatar christianscott avatar felixdavidson avatar jibby0 avatar kimsaehun avatar praveensvsrk avatar riquellopes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

curry.py's Issues

Arguments persist between function calls

The args object is created when the function is curried. That means that the arguments keep getting added each time the function is called, oops...

If we add a print statement to the curried function, this is what is printed out:

>>> add(1)(2)
[]
[1]
3
>>> add(1)(2)
[1, 2]
[1, 2, 1]

Thanks to @praveensvsrk for pointing this out!

Write more unit tests

I'm sure there are even more cases where this function fails in its' current form, it'd be great to find them!

Use default arguments

Currently there's no way to use default arguments. For example:

@curry
def add(a, b=10):
    return a + b

x = add(10) # sometimes will want this to be 20, but *always* returns another function

Will need to think of some way to specify that this is the behaviour we want. There are two ways that I can imagine:

  • @curry(use_default=True) - less flexible, harder to implement
  • add(10, done=True) - more flexible, easy to implement (I like this better)

Allow passing of keyword arguments

This will require some pretty big changes to the way the function works. Will need to seperate the signature(fun).parameters list into two: args and kwargs.

The kwargs could be stored in a dict, then could check if the dict has enough keys of the right type before calling the function.

Not sure how this would interact with default values.

Something like this:

def curried(*args, **kwargs):
    assert only_one_has_value(args, kwargs)
    if kwargs:
       # set value
    if len(arguments) == arg_count or all_keys_set(kwarguments, needed_keys):
        ...

Doesn't work on single argument functions

Doesn't work on single argument functions (precisely, single call)

>>> increment = curry(lambda x: x + 1)
<function <lambda> at 0x030F90C0>

>>> increment(3) # curried is returned here, a side effect of using the two level nesting
<function <lambda> at 0x030EA540>

>>> increment(3)() #had to explicitly call curried
4

Also, this:

>>> add3 = curry(lambda a, b, c: a + b + c)

>>> add3(1,2)(3) # works as expected
6

>>> add3(1,2,3) # supplied all the arguments in one go, returns curried
<function <lambda> at 0x030EA660>

>>> add3(1,2,3)() # explicit call
6

Possible fix:
In the curried_factory function, first check if we already have the required arguments. If yes, call the function, otherwise return curried

Not working with *args

Here's an example:

>>> @curry
... def prefix_all_with(prefix, *words):
...     return [prefix + word for word in words]
...
>>> un_ify_all = prefix_all_with("un")
>>> un_ify_all("cool", "fun")
<function prefix_all_with at 0x10d7039d8>
>>> un_ify_all("cool", "fun")()
<function prefix_all_with at 0x10d703ae8>
>>> un_ify_all("cool", "fun")()()
<function prefix_all_with at 0x10d7039d8>
>>>

I imagine this has something to do with have_enough_args

Wraps function

You could use the wraps function, it already does everything you need when you are working with decorator.

from functools import wraps

def curry(func):
   @wraps(func)
   def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Add clear explanation of how the curry function works

It would be nice if there was an explanation of how it works for new contributors. Particularly:

  • Why are there two nested functions?
  • Why is the nonlocal keyword being used?

Either via comments or in the README.

Remove Executable Bit From `curry.py`

$ git clone https://github.com/chrfrasco/curry.py.git
$ ls -l curry.py/curry.py
-rwxr-xr-x 1 bew bew 1640 Oct 11 13:32 curry.py/curry.py*

curry.py isn't a script, so I think it shouldn't be executable.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.