[Python-ideas] Partial operator (and 'third-party methods' and 'piping')

Koos Zevenhoven koos.zevenhoven at aalto.fi
Mon May 11 00:42:27 CEST 2015


Hi everyone!

(Sorry about double posting, but I wanted to start a new thread, which I 
tried but apparently failed to do last time. Although inspired by and 
related to the function composition discussion, this is now something 
different and should not be cluttering the composition thread.)

Reading the recent emails in the function composition thread started by 
Ivan, I realized that my sketch for a composition operator (from 
yesterday, quoted below) would be much better if it did not actually do 
function composition . Instead, -> would be quite powerful as 'just' a 
partial operator -- perhaps even more powerful, as I demonstrate below. 
However, this is not an argument against @ composition, which might in 
fact play together with this quite nicely.

This allows some nice things with multi-argument functions too.

I realize that it may be unlikely that a new operator would be added, 
but here it is anyway, as food for thought.  (With an existing operator, 
I suspect it would be even less likely, because of precedence rules : )

So, -> would be an operator with a precedence similar to .attribute 
access (but lower than .attribute):

  # The simple definition of what it does:
  arg->func   # equivalent to functools.partial(func, arg)

This would allow for instance:
  arg -> spam() -> cheese(kind = 'gouda') -> eggs()

which would be equivalent to eggs(cheese(spam(arg), kind = 'gouda'))

Or even together together with the proposed @ composition:
  rms = root @ mean @ square->map     # for an iterable non-numpy argument

And here's something I find quite interesting. Together with 
@singledispatch from 3.4 (or possibly an enhanced version using type 
annotations in the future?), one could add 'third-party methods' to 
classes in other libraries without monkey patching. A dummy example:

from numpy import array
my_list = [1,2,3]
my_array = array(my_list)
my_mean = my_array.mean()  # This currently works in numpy

from rmslib import rms
my_rms = my_array->rms()  # efficient rms for numpy arrays
my_other_rms = my_list->rms()  # rms that works on any iterable

One would be able to distinguish between calls to methods and 
'third-party methods' based on whether . or -> is used for accessing 
them, which I think is a good thing. Also, third-party methods would be 
less likely to mutate the object, just like func(obj) is less likely to 
mutate obj than obj.method().

See more examples below. I converted my examples from last night to this 
IMO better version, because at least some of them would still be relevant.

On 10.5.2015 2:07, Koos Zevenhoven wrote:
> On 10.5.2015 1:03, Gregory Salvan wrote:
>> Nobody convinced by arrow operator ?
>>
>> like: arg -> spam -> eggs -> cheese
>> or cheese <- eggs <- spam <- arg
>>
>>
>
> I like | a lot because of the pipe analogy. However, having a new 
> operator for this could solve some issues about operator precedence.
>
> Today, I sketched one possible version that would use a new .. 
> operator. I'll explain what it would do (but with your -> instead of 
> my ..)
>
> Here, the operator (.. or ->) would have a higher precedence than 
> function calls () but a lower precedence than attribute access 
> (obj.attr).
>
> First, with single-argument functions spam, eggs and cheese, and a 
> non-function arg:
>
> arg->eggs->spam->cheese()   # equivalent to cheese(spam(eggs(arg)))

With -> as a partial operator, this would instead be:

arg->eggs()->spam()->cheese()     # equivalent to cheese(spam(eggs(arg)))

> eggs->spam->cheese # equivalent to lambda arg: cheese(spam(eggs(arg)))
>

With -> as a partial operator this could be:

lambda arg: arg->eggs()->spam()->cheese()


> Then, if spam and eggs both took two arguments; eggs(arg1, arg2), 
> spam(arg1, arg2)
>
> arg->eggs   # equivalent to partial(eggs, arg)
> eggs->spam(a, b, c)   # equivalent to spam(eggs(a, b), c)

With -> as a partial operator, the first one would work, and the second 
would become:

eggs(a,b)->spam(c)     # equivalent to spam(eggs(a, b), c)

> arg->eggs->spam(b,c) # equivalent to spam(eggs(arg, b), c)
>

This would become:

arg->eggs(b)->spam(c)     # equivalent to spam(eggs(arg, b), c)

Note that this would be quite flexible in partial 'piping' of 
multi-argument functions.

> So you could think of -> as an extended partial operator. And this 
> would naturally generalize to functions with even more arguments. The 
> arguments would always be fed in the same order as in the equivalent 
> function call, which makes for a nice rule of thumb. However, I 
> suppose one would usually avoid combinations that are difficult to 
> understand.
>
> Some examples that this would enable:
>
>  # Example 1
>  from numpy import square, mean, sqrt
>  rms = square->mean->sqrt  # I think this order is fine because it is 
> not @
>

This would become:

def rms(arr):
     return arr->square()->mean()->sqrt()

>  # Example 2 (both are equivalent)
>  spam(args)->eggs->cheese() # the shell-syntax analogy that Steven 
> mentioned.
>

This would be:

spam(args)->eggs()->cheese()

Of course the shell piping analogy would be quite far, because it looks 
so different.

>  # Example 3
>  # Last but not least, we would finally have this
>  some_sequence->len()
>  some_object->isinstance(MyType)
>

And:

  func->map(seq)
  func->reduce(seq)

-- Koos


More information about the Python-ideas mailing list