zeehio / parmap

Easy to use map and starmap python equivalents
Apache License 2.0
144 stars 9 forks source link

Iterable as a second argument #15

Open wjaskowski opened 6 years ago

wjaskowski commented 6 years ago

It is possible to easily achieve something like:

def f(x, y):
    r = 0
    for i in range(100000000000):
        r += i
    return r
parmap.map(f, x=1, y=range(100))

?

zeehio commented 6 years ago

Yes, just use a helper function:

def g(y, x):
    return f(x, y)

parmap.map(g, range(100), x=1)

There is also https://docs.python.org/3.6/library/functools.html#functools.partial

If you have a suggestion for an alternative API, feel free to suggest it.

wjaskowski commented 6 years ago

I was thinking that maybe map can assign the iterable to y because the value of x was given explicitly. Similarly, starmap could assign iterables to the subsequent arguments that were not explicitly provided in **kwargs.

On 16 March 2018 at 19:46, Sergio Oller notifications@github.com wrote:

Yes, just use a helper function:

def g(y, x): return f(x, y)

parmap.map(g, range(100), x=1)

There is also https://docs.python.org/3.6/library/functools.html# functools.partial

If you have a suggestion for an alternative API, feel free to suggest it.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/zeehio/parmap/issues/15#issuecomment-373809314, or mute the thread https://github.com/notifications/unsubscribe-auth/AEuSHZ9syigC7E8FPBc3IJI6w4TxzP5Wks5tfAhtgaJpZM4St3tA .

zeehio commented 6 years ago

I have some concerns with that approach:

There is a use case where an extra argument is an iterable:

def times2(xlimits, x):
    xmin = xlimits[0]
    xmax = xlimits[1]
    if x < xmin:
        x = xmin
    if x > xmax:
        x = xmax
    return 2*x

parmap.map(times2, xlimits = [0, 5], x = range(10))

It would be harder (impossible) in this case for parmap to guess that the iterable argument is the second one.

It is then possible to modify times2 so as if xlimits is a single number it assumes it is a single boundary (xmax) and, if it is a two element list then it follows the previous definition:

def times2b(xlimits, x):
    if isinstance(xlimits, (int, float)):
        xmin = -float('Inf')
        xmax = xlimits
    else: 
        xmin = xlimits[0]
        xmax = xlimits[1]
    if x < xmin:
        x = xmin
    if x > xmax:
        x = xmax
    return 2*x

parmap.map(times2, xlimits = 5, x = range(10))
parmap.map(times2, xlimits = [0, 5], x = range(10))

Here with your API proposal, the first call would work as expected, the second one would not do what we wanted. It could surprise parmap users.

For an interactive use (such as in a notebook) the approach you suggest is good, in most cases it will do what we want and we will like it.

However, parmap users may start to use that syntax in their python packages, and their users may use those packages in surprising ways that can lead to parmap iterating in the wrong argument.

https://en.m.wikipedia.org/wiki/Principle_of_least_astonishment

Besides, by reading at your first example I could also think that you wanted to compute f(x=1, y=range(100)) and that you meant: parmap.map(f, x=[1], y=range(100)). Currently we can catch the error while with your API we would get a surprising result.

What would you think of an API like:

parmap.map(f, x=1, y=range(100), pm_iter="y")

?

wjaskowski commented 6 years ago

My idea was that

parmap.map(times2, xlimits = [0, 5], x = range(10))

should simply call times2 with the given arguments but

parmap.map(times2, range(10), xlimits = [0, 5])
parmap.map(times2, iterable=range(10), xlimits = [0, 5])

could call times2 with different values of x. Do you find it surprising?

At the same time, I like

parmap.map(f, x=1, y=range(100), pm_iter="y")

It is nicely explicit.