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

toolz.merge_with not lazy, breaks with Clojure interface #153

Open
mrocklin opened this issue Mar 28, 2014 · 2 comments
Open

toolz.merge_with not lazy, breaks with Clojure interface #153

mrocklin opened this issue Mar 28, 2014 · 2 comments

Comments

@mrocklin
Copy link
Member

Clojure's core.merge-with takes a binary operator

(defn add [a b]
  (+ a b))  ; yes I know this is silly, I wanted an explicit binary operator

(merge-with add [{:a 1} {:a 2} {:a 3}])
;; {:a 6}

Toolz merge_with takes a reduction operation

>>> merge_with(sum, [{'a': 1}, {'a': 2}, {'a': 3}])
{'a': 6}

This is convenient from a Python perspective because operations like sum are more familiar than binary operators like add. However this is not lazy and requires all dicts to be read in at once.

I tried a solution in #127 but gave up.

@eriknw
Copy link
Member

eriknw commented Mar 28, 2014

Although this doesn't address the laziness issue, one option to deal with any binop (including builtins) is to do a try-except as follows:

try:
    # current method
    return dict((k, func(v)) for k, v in iteritems(result))
except TypeError:
    # perform reduction assuming `func` is a binary function
    ...

This isn't perfect. What if the function returned an actual TypeError? Also, if func is a curried function that needs two more inputs, then it won't be reduced as expected.

Regarding the attempt in #127, I don't like relying on inspect.

Two other options include:

  1. Make a new function
  2. Add a keyword argument to choose automatic (and lazy) binop reduction.
    • A keyword option may also allow variadic calling of a function, such as func(*vals).
    • For example, merge_with(func, *dicts, **functype), where keyword functype may be unary (default), binary, or variadic.

@eriknw
Copy link
Member

eriknw commented Apr 8, 2014

One solution is to add init as a keyword parameter. If it is given, then use func as a binary operator, otherwise use func as a unary operator.

This would require changing the API to def merge_with(func, *dicts, **init) or def merge_with(func, dicts, init=no_init). Side note: def merge_with(func, *dicts, init=no_init) is possible in Python 3.

The strategy to choose func as binop or unop based on whether init is provided may also work for reduceby, which is being discussed here: pytoolz/cytoolz#1

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