From: Thomas Jollans on
On 07/18/2010 05:52 PM, Reid Kleckner wrote:
> Usual disclaimer: python-dev is for the development *of* python, not
> *with*. See python-list, etc.

Moving to python-list. Please keep discussion there.

>
> That said, def declares new functions or methods, so you can't put
> arbitrary expressions in there like type(f).__mul__ .
>
> You can usually assign to things like that though, but in this case
> you run into trouble, as shown below:
>
>>>> def func(): pass
> ...
>>>> type(func)
> <class 'function'>
>>>> def compose(f, g):
> ... return lambda x: f(g(x))
> ...
>>>> type(func).__mul__ = compose
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: can't set attributes of built-in/extension type 'function'
>
> As the interpreter says, it doesn't like people mucking with operator
> slots on built in types.
>
> Finally, if you like coding in that very functional style, I'd
> recommend Haskell or other ML derived languages. Python doesn't
> support that programming style very well by choice.
>
> Reid
>
> On Sun, Jul 18, 2010 at 8:34 AM, Christopher Olah
> <christopherolah.co(a)gmail.com> wrote:
>> Dear python-dev,
>>
>> In mathematical notation, f*g = z->f(g(z)) and f^n = f*f*f... (n
>> times). I often run into situations in python where such operators
>> could result in cleaner code. Eventually, I decided to implement it
>> myself and see how it worked in practice.
>>
>> However, my intuitive implementation [1] doesn't seem to work. In
>> particular, despite what it says in function's documentation, function
>> does not seem to be in __builtin__. Furthermore, when I try to
>> implement this through type(f) (where f is a function) I get invalid
>> syntax errors.
>>
>> I hope I haven't made some trivial error; I'm rather inexperienced as
>> a pythonist.
>>
>> Christopher Olah
>>
>>
>> [1] Sketch:
>>
>> def __builtin__.function.__mul__(self, f):
>> return lambda x: self(f(x))
>>
>> def __builtin__.function.__pow__(self, n):
>> return lambda x: reduce(lambda a,b: [f for i in range(n)]+[x])


As Reid explained, you can't just muck around with built-in types like
that. However, you can "use a different type".

If you're not familiar with Python decorators, look them up, and then
have a look at this simple implementation of what you were looking for:

>>> class mfunc:
.... def __init__(self, func):
.... self.func = func
.... self.__doc__ = func.__doc__
.... self.__name__ = func.__name__
.... def __call__(self, *args, **kwargs):
.... return self.func(*args, **kwargs)
.... def __mul__(self, f2):
.... @mfunc
.... def composite(*a, **kwa):
.... return self.func(f2(*a, **kwa))
.... return composite
.... def __pow__(self, n):
.... if n < 1:
.... raise ValueError(n)
.... elif n == 1:
.... return self.func
.... else:
.... return self * (self ** (n-1))
....
>>> @mfunc
.... def square(x): return x*x
....
>>> @mfunc
.... def twice(x): return 2*x
....
>>> (square*twice)(1.5)
9.0
>>> addthree = mfunc(lambda x: x+3)
>>> addfifteen = (addthree ** 5)
>>> addfifteen(0)
15
>>>


From: Christopher Olah on
Firstly, apologies for posting to the wrong list. Since I was fiddling
around with a modification to the language, if the implementation
details of something I'd never expect to get accepted, I'd thought
python-dev might be appropriate... In retrospect, it is fairly clear
that it was the wrong choice.

Secondly, the problem with using decorators is that it doesn't make it
so that all functions do this by default. If one is going to decorate
every function they use/declare, lambdas look preferable.

In any case, thank you for your help.

Christopher


On Sun, Jul 18, 2010 at 1:43 PM, Thomas Jollans <thomas(a)jollans.com> wrote:
> On 07/18/2010 05:52 PM, Reid Kleckner wrote:
>> Usual disclaimer: python-dev is for the development *of* python, not
>> *with*.  See python-list, etc.
>
> Moving to python-list. Please keep discussion there.
>
>>
>> That said, def declares new functions or methods, so you can't put
>> arbitrary expressions in there like type(f).__mul__ .
>>
>> You can usually assign to things like that though, but in this case
>> you run into trouble, as shown below:
>>
>>>>> def func(): pass
>> ...
>>>>> type(func)
>> <class 'function'>
>>>>> def compose(f, g):
>> ...     return lambda x: f(g(x))
>> ...
>>>>> type(func).__mul__ = compose
>> Traceback (most recent call last):
>>   File "<stdin>", line 1, in <module>
>> TypeError: can't set attributes of built-in/extension type 'function'
>>
>> As the interpreter says, it doesn't like people mucking with operator
>> slots on built in types.
>>
>> Finally, if you like coding in that very functional style, I'd
>> recommend Haskell or other ML derived languages.  Python doesn't
>> support that programming style very well by choice.
>>
>> Reid
>>
>> On Sun, Jul 18, 2010 at 8:34 AM, Christopher Olah
>> <christopherolah.co(a)gmail.com> wrote:
>>> Dear python-dev,
>>>
>>> In mathematical notation, f*g = z->f(g(z)) and f^n = f*f*f... (n
>>> times). I often run into situations in python where such operators
>>> could result in cleaner code. Eventually, I decided to implement it
>>> myself and see how it worked in practice.
>>>
>>> However, my intuitive implementation [1] doesn't seem to work. In
>>> particular, despite what it says in function's documentation, function
>>> does not seem to be in __builtin__. Furthermore, when I try to
>>> implement this through type(f) (where f is a function) I get invalid
>>> syntax errors.
>>>
>>> I hope I haven't made some trivial error; I'm rather inexperienced as
>>> a pythonist.
>>>
>>> Christopher Olah
>>>
>>>
>>> [1] Sketch:
>>>
>>> def __builtin__.function.__mul__(self, f):
>>>    return lambda x: self(f(x))
>>>
>>> def __builtin__.function.__pow__(self, n):
>>>    return lambda x: reduce(lambda a,b: [f for i in range(n)]+[x])
>
>
> As Reid explained, you can't just muck around with built-in types like
> that. However, you can "use a different type".
>
> If you're not familiar with Python decorators, look them up, and then
> have a look at this simple implementation of what you were looking for:
>
>>>> class mfunc:
> ...     def __init__(self, func):
> ...         self.func = func
> ...         self.__doc__ = func.__doc__
> ...         self.__name__ = func.__name__
> ...     def __call__(self, *args, **kwargs):
> ...         return self.func(*args, **kwargs)
> ...     def __mul__(self, f2):
> ...         @mfunc
> ...         def composite(*a, **kwa):
> ...             return self.func(f2(*a, **kwa))
> ...         return composite
> ...     def __pow__(self, n):
> ...         if n < 1:
> ...             raise ValueError(n)
> ...         elif n == 1:
> ...             return self.func
> ...         else:
> ...             return self * (self ** (n-1))
> ...
>>>> @mfunc
> ... def square(x): return x*x
> ...
>>>> @mfunc
> ... def twice(x): return 2*x
> ...
>>>> (square*twice)(1.5)
> 9.0
>>>> addthree = mfunc(lambda x: x+3)
>>>> addfifteen = (addthree ** 5)
>>>> addfifteen(0)
> 15
>>>>
>
>
>
From: Terry Reedy on
>> Christopher Olah
>>> In mathematical notation, f*g = z->f(g(z)) and f^n = f*f*f... (n
>>> times). I often run into situations in python where such operators
>>> could result in cleaner code.

Python has a general mechanism for composing functions to make new
functions: the def statement. "z = f*g" is a special case operation
combining two compatible one-parameter parameter functions. In Python,
it is spelled
def z(x): return f(g(x))
The advantage of the latter is that it gives the resulting function
object a definition name attached to the function as an attribute, which
is important for error tracebacks.

This gets to a difference between math and computing. In math, 'z=f*f',
if it is not an equality claim ('z==f*g' in Python terms), defines 'z'
to mean the *pre-existing*, abstract, attribute-less function that can
also be denoted by 'f*g'. In Python (in particular), it would mean
"create a *new*, anonymous function object and associate it
non-exclusively with 'z'".

If f and g are not primitive functions but are compositions themselves,
then substituting the composition for g in the composition for f may
allow for simplification and greater efficiency. This consideration is
irrelevant in math, where computation happens instantaneously, or where
f*g is simply a set of ordered pairs, just like f and g (all with
instataneous lookup).

As for f^n, it is very rare in practice for n to be a fixed value more
than 2 or 3. For n==2, f^2 is simply f*f, see above. For n==3,
def f3(x): return f(f(f(x)))
has the advantage of creating one new function instead of two. I believe
larger values of n mostly arise in iteration to a fixed point or until
some other stopping point in reached.

Terry Jan Reedy