beartype / plum

Multiple dispatch in Python
https://beartype.github.io/plum
MIT License
497 stars 22 forks source link

Set precedence of individual types #140

Open sylvorg opened 1 month ago

sylvorg commented 1 month ago

Hello, and sorry for bothering you so many times! 😅

As in the title; is there any way to set the precedence of individual types? For example, using beartype.vale.Is, I have cobbled together an annotation ExactOrigin which should be tested before Callable, for example. But when I use those two annotations in a plum function, I get an AmbiguousLookupError. Is there any way to set the precedence of ExactOrigin over Callable?

Thank you kindly for the help!

wesselb commented 1 month ago

Hey @sylvorg, absolutely not a problem! I massively appreciate your issues. Please feel free to keep opening them!! I'll do my best to respond as quickly as possible, though sometimes I won't have the capacity to get back to you quickly.

Currently, there is no way to set the precedence of individual types. Ideally, you would create this precedence by making the one type a subtype of another. In your case, I think your problem would be solved by making ExactOrigin a subtype of Callable. Would that be possible?

sylvorg commented 1 month ago

No worries, take all the time you need! Greatness can't be rushed, after all!

The problem with making ExactOrigin a subtype is that ExactOrigin, and a host of other checks I've created, can't use beartype.vale.Is, which seems to only work with the Annotated hint; is there any way to implement that in a way that'll keep the signature of ExactOrigin, such as when using get_origin on something like ExactOrigin[type] and getting back ExactOrigin? ... Sorry, I may have gotten a little confused at the end there, so if you have any questions, please let me know! 😅

sylvorg commented 1 month ago

Also, I'd like Literal or Union to be checked before Callable, for example! 😅 Basically, anything with a smaller subset of possible types!

wesselb commented 1 month ago

@sylvorg would you be able to give a full MWE of what you'd like to achieve, the one with beartype.vale.Is and ExactOrigin?

sylvorg commented 1 month ago

Basically, Origin and ExactOrigin would check the origin of an annotation, such as tuple[...] being tuple, and if the origin is None, such as for just tuple, the annotation itself is used, i.e., again, tuple. I recently raised an issue in the beartype repo to address some issues with beartype.door.is_subhint, so hopefully I won't need those anymore, but I have other Annotation types as well, such as Not (which checks that something is not in the subscripted arguments), Exclude (which takes an annotation and returns false for subtypes, such as checking if something is an integer but not a boolean), etc.

Would you happen to know how I can turn these into full-fledged classes that I can subscript as well? Because I don't know how to use __args__ of a types.GenericAlias object, for example.

wesselb commented 2 weeks ago

Hey @sylvorg! Apologies for the delay. It's a busy and hectic time.

Plum relies on a so-called partial order between types (basically, a <= relationship). That this is a partial order is very important, because it ensures correctness of dispatch. Currently, Plum uses beartype.door.TypeHint to give this partial order.

What you seem to want to achieve is to slightly modify the partial order currently defined by beartype.door.TypeHint. I'm a little reluctant to build this into Plum, since correctness of the partial order is crucial to the correctness of dispatch.

However, what has come up before, and what would be feasible, is to make the "type system" configurable for every dispatcher.

You would be able to do something like this:

def partial_order(type1, type2):
    return TypeHint(type1) <= TypeHint(type2)

dispatch = Dispatcher(type_system=partial_order)

You could then base your partial_order off of @beartype's TypeHint and insert your optimisations.

How would something like that sound?