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

Missing mechanism to enforce/check parameters interior mutability/inmutability #6947

Closed
castarco opened this issue Jun 7, 2019 · 6 comments

Comments

@castarco
Copy link

castarco commented Jun 7, 2019

Disclaimer: Not sure if this "feature request" should belong to Mypy or to Python itself in the form of RFC/PEP document(s). In any case, I would very like to be involved in this kind of stuff, so I would be very grateful about any kind of guidance on the topic.

First of all, this could be split into two different related issues, but for now I prefer to keep both together:

  • As far as I know, there's no clear way to hint that a method does not mutate the inner state of the passed parameters.
  • As far as I know, there's no clear way to indicate that a method does not change the internal state of its associated object (directly, or indirectly, through one of its properties). In fact, in Python this could be though as a special case of the first point, given that we pass the self reference as a parameter.

There are various possible approaches, but I think they could be summarized to two: trying to mark the whole function as "pure", or marking each parameter as "safe from being mutated" individually.

Why this could be interesting? Well, it could give extra guarantees to the consumers/users of some functions and/or methods, and also could make easier to spot implementation problems.

  • Extra consideration 1: the same could be done in the opposite direction: marking some parameters to hint that they will be mutated, helping programmers to be aware of potential side effects.
  • Extra consideration 2: This could be possibly discussed in a different thread, in order to have a complete notion on whether a function is "pure" or not, marking that it does not mutate global state is also important, also marking the opposite, although that's not so useful.

Note: Even if Mypy/Pyre/Pycharm are not able to do anything with the annotations, I believe starting to think on how they should look is a first important step.

@castarco castarco changed the title Missing mechanism to enforce/check parameters interior inmutability Missing mechanism to enforce/check parameters interior mutability/inmutability Jun 7, 2019
@JukkaL
Copy link
Collaborator

JukkaL commented Jun 10, 2019

This has been discussed in various contexts previously, starting from the early days of Python static type checking, but it has never moved forward, perhaps because nobody has been able to come up with a workable proposal.

Marking functions as pure is difficult, since for it to work as I'd expect, library functions without side effects would also have to be annotated as pure, which would be a big undertaking. For example, a pure method should probably be able to call list.find but not list.append. Since practical support would likely require lots of changes to library stubs, this will be a difficult feature to adopt and even experiment with.

If you still think that this is worth pursuing, my recommendation is to write a concrete proposal about what it would look like, with a focus on explaning how this would work with real-world code. Otherwise it may be hard to get people interested.

The typing repository or the typing-sig@ mailing list are better places to discuss new type system features, since maintainers of other type checking tools also follow them, and we generally try to standardize major new mypy type system features.

@lint-ai
Copy link

lint-ai commented Jun 10, 2019

As far as I know, there's no clear way to indicate that a method does not change the internal state of its associated object (directly, or indirectly, through one of its properties). In fact, in Python this could be though as a special case of the first point, given that we pass the self reference as a parameter.

This isn't quite strictly true, but it's mostly accurate to say that @staticmethod (and to a lesser but still substantial extent, @classmethod) do exactly this, given that it takes some fairly creative reflection gymnastics to mutate the "owning" object of a static or class method.

@castarco
Copy link
Author

This isn't quite strictly true, but it's mostly accurate to say that @staticmethod (and to a lesser but still substantial extent, @classmethod) do exactly this, given that it takes some fairly creative reflection gymnastics to mutate the "owning" object of a static or class method.

Well, but it also affects the possibility of reading the object's parameters, since we don't pass the self reference. As I pointed out, this was a specific case of a more general one: ensuring that we don't mutate the passed parameters.

@castarco
Copy link
Author

castarco commented Jun 13, 2019

Marking functions as pure is difficult, since for it to work as I'd expect, library functions without side effects would also have to be annotated as pure, which would be a big undertaking.

Totally true, I was thinking that, given that the typing is something optional, we could create some kind of "good-faith" annotation/comment to mark the calls to not annotated methods, that the type checker should trust (I'm not considering the possibility of "adversarial programmers" that mess with the typing system on purpose).

That way it should be easier to incorporate these changes gradually, without having to undertake a big effort in one single release, but of course the burden would be on the caller side, at least for some time.

Maybe something like:

def foo(x: Immutable[C]) -> str:
    # We attach a local annotation to a function/callable, so the type checker won't complain
    # for calling a function that does not provide the guarantees that `foo`'s signature is
    # promising.
    #
    # annotate(bar): Callable[[Immutable[C]], str]
    return bar(x)

def moo(x: Immutable[C]) -> str:
    # This should generate a warning, because the annotation is redundant
    # annotate(foo): Callable[[Immutable[C]], str]
    return foo(x)

def func(x: Immutable[C]) -> int:
    # This should generate an error, because the annotation is not compatible
    # annotate(foo): Callable[[Immutable[C]], int]
    return foo(x)

Although could be that this mechanism is general enough for other use cases, not sure though.

@castarco
Copy link
Author

Regarding how to start a discussion about this in the typing repository, I have three points now, and not sure if I should comment all together or open different issues for each of them:

  • local-scoped annotations for "global" functions and/or objects, to make the type checker happy when the stubs' annotations are not fine-grained enough.
  • Mutable/Immutable annotations for parameters & variables (which could be more useful after having the local-scoped annotations for global stuff).
  • NoGlobalSideEffects annotation (or something similar) for functions (this is not related to parameters, but to the mutation of global objects).

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

3 participants