[Python-ideas] Statically checking purity of functions

Christoph Groth christoph at grothesque.org
Thu Jun 9 17:59:32 EDT 2016


Cory Benfield wrote:

> Here’s where this falls down (NB, the print is just a simple 
> example of non-purity, but you can imagine that you can just do 
> arbitrary code).
>
> class SideEffectyInt(int):
>     def __add__(self, other):
>         if other == 3:
>             print(“YAY")
>         return super().__add__(other)

Of course, an instance of SideEffectInt is also an instance of int 
and as such mypy will not complain when it is passed to the "add" 
function.

However, using the above technique any type checking by mypy can 
be circumvented.  Mypy will happily nod through the following 
program:

class FloatingInt(int):
    def __add__(self, other):
        return float(super().__add__(other))

def add(a: int, b: int) -> int:
    return a + b

print(add(FloatingInt(1), 2))

However, if we change the above class definition to

class FloatingInt(int):
    def __add__(self, other) -> float:
        return float(super().__add__(other))

mypy will start to complain with the following error:

test.py:2: error: Return type of "__add__" incompatible with 
supertype "int"

Now, mypy could be probably more strict and flag already the first 
definition of FloatingInt.__add__() as an error, because it knows 
(as the above error message shows) what its return type should be 
From inheritance.  It also knows that float()  produces a float, 
since it does not accept

def foo() -> int:
    return float(5)

I guess it's a choice of mypy not to complain about bits of code 
that don't use type declarations.

In an exactly analogous way, mypy could detect that calling 
print() in SideEffectlyInt.__add__ is incompatible with the 
(supposedly declared) purity of int.__add__() if SideEffectlyInt 
was declared as:

class SideEffectyInt(int):
    @pure
    def __add__(self, other):
        if other == 3:
            print(“YAY")
        return super().__add__(other)

And just like before, if mypy was more strict, it could detect the 
purity-breaking of SideEffectyInt.__add__() from inheritance.

> This SideEffectyInt() object is suitable for passing into your 
> pure “add” function, but has an impure add logic. I’m not sure 
> how mypy could resolve this problem: I presume it’d need purity 
> declarations for every possible function on every type *and* the 
> ability to see all function invocations, which is pretty 
> difficult given that Python’s dynamism makes it possible to 
> invoke arbitrary functions.

This is correct, but I believe your statement remains correct when 
one replaces "purity" by "typing": In order to verify with 
certainty that the typing is correct for a function, mypy would 
have to know the typing declaration of every function that is 
called inside it.

> So I don’t really see how this pure decorator could *enforce* 
> purity.

It seems to me that purity could be enforced to the same degree 
that correct typing can be enforced.  In other words, the goal of 
mypy seems not to be to prove that typing of a program is 
consistent (Python is too dynamic for that), but rather to help 
finding possible errors.  It seems to me that the same service 
could be provided for purity.  That should be good enough for many 
applications.

In that spirit, perhaps it would make more sense to assume purity 
by default if unspecified (just like mypy assumes type correctness 
when no typing information is present), and have both "pure" and 
"impure" (dirty? ;-) decorators.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 818 bytes
Desc: not available
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20160609/fdc01fcd/attachment-0001.sig>


More information about the Python-ideas mailing list