Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make curry idempotent such that curry(curry(f)) -> curry(f) #147

Closed
eriknw opened this issue Mar 18, 2014 · 10 comments
Closed

Make curry idempotent such that curry(curry(f)) -> curry(f) #147

eriknw opened this issue Mar 18, 2014 · 10 comments

Comments

@eriknw
Copy link
Member

eriknw commented Mar 18, 2014

This may be done by having a Curry class and a curry function.

@mrocklin
Copy link
Member

Yeah, I was thinking about how best to do that. I prefer class Curry/def curry solution to Curry.__new__. Another option is to unpack the func, args, and kwargs from the argument curried function and then repack them into self.

@eriknw
Copy link
Member Author

eriknw commented Mar 18, 2014

Yeah, I considered those options too. I don't want to use __new__.

Consider the following:

@curry
def f(x, y):
    return x + y

g = curry(f)

g is f

Do we want g is f to evaluate as True or False? This is the main difference between the other two options.

@mrocklin
Copy link
Member

I don't have a strong opinion one way or the other. Both seem like reasonable options. What is your opinion?

@eriknw
Copy link
Member Author

eriknw commented Mar 18, 2014

I'm leaning towards unpacking func, args, and kwargs, but my paranoia has not yet run its course.

@mrocklin
Copy link
Member

Making a separate curry function seems totally reasonable. It's what we do for compose.

@eriknw
Copy link
Member Author

eriknw commented Mar 18, 2014

Yeah, both seem reasonable, and both behaviors are of course possible with curry as a function. def curry/class Curry does seem to be the most clean.

If f is a curry instance and g = f(some_args) is still a curry or partial instance, then I would expect h = curry(g) to make h an instance of curry regardless of whether g is a curry or partial instance, which implies (g is h) is False.

How much of an advantage is there for a curried object to return a partial if only one more argument is needed? Having multiple return types muddies the waters a bit.

@mrocklin
Copy link
Member

The performance increase for using partial was substantial. This is particularly important for cases like

map(get(0), lots_of_data)

@eriknw
Copy link
Member Author

eriknw commented Mar 18, 2014

Figured as much, because you wouldn't have bothered with the hassle otherwise.

Okay, so lets make curry a function and Curry a class. The curry function will always return a new functools.partial (if appropriate) or Curry instance. If the callable passed to curry is an instance of functools.partial or Curry, then the func, args, and keywords attributes will be used to instantiate a new object (along with any additional *args and **kwargs passed to curry of course).

eriknw added a commit to eriknw/toolz that referenced this issue Mar 19, 2014
…Not finished.

This includes a significant change to curried objects: they never return
`functools.partial` objects.  Instead, `Curry` *uses* `partial`.  This
is because `partial` behaves differently.  Let me illustrate:

`curry(lambda x, y=0: x+y)(y=2)` is valid.
`partial(lambda x, y=0: x+y)(y=2)` returns a TypeError.

More documentation and tests are needed.  PRs (and comments) are welcome.
@eriknw
Copy link
Member Author

eriknw commented Mar 19, 2014

I have created a branch to address this issue (https://github.com/eriknw/toolz/tree/curry_class). It is not yet ready, but feedback and PRs are most welcome.

The main change is that a curried instance uses functools.partial, but never returns a functoolz.partial instance. This is because curry and partial behave differently. For example, doing @memoize(cache={1: 2}) doesn't work if memoize is partial.

I have not performed any benchmarks, and the pickle protocol still needs implemented.

eriknw added a commit to eriknw/toolz that referenced this issue May 6, 2014
…#155 pytoolz#159

1. Idempotent such that `curry(curry(f))` is equivalent to `curry(f)`; see pytoolz#147
2. Comparable via equality and hashable based on `func`, `args`, and `keywords`; see pytoolz#159
3. `func`, `args`, and `keywords` attributes are readonly
4. `__name__` and `__doc__` attributes are writable
5. More efficient implementation using `partial`; see pytoolz#150
6. Curried objects never transmogrigy into `partial` objects; see pytoolz#155
mrocklin added a commit that referenced this issue May 6, 2014
Many improvements to `curry` based on #147 #150 #155 #159
@eriknw
Copy link
Member Author

eriknw commented May 6, 2014

Closing. Idempotency implemented in #170

@eriknw eriknw closed this as completed May 6, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants