Skip to content
This repository has been archived by the owner on Oct 4, 2024. It is now read-only.

Add support for matching using context managers. #37

Open
aarondewindt opened this issue May 16, 2020 · 4 comments
Open

Add support for matching using context managers. #37

aarondewindt opened this issue May 16, 2020 · 4 comments

Comments

@aarondewindt
Copy link

aarondewindt commented May 16, 2020

One issue I have with the match function is that it relies on either using lambdas (which can only contain one line) or if you need multiple lines, first defining the handler function and then passing it to the match function.

An option that I think is worth considering is using context managers for pattern matching. The main drawback is that it would not allow returning a value like with the match function, but the syntax is nicer when the case handlers contain more than one statement. As a proof of concept I have implemented this new interface while keeping the library backwards compatible.

Here is some example code of the new proposed syntax.

@adt
class ContextMatching:
    EMPTY: Case
    INTEGER: Case[int]
    STRINGS: Case[str, str]

foo = ContextMatching.INTEGER(1)

with foo.empty:
    print("Is empty")

with foo.integer as value:
    print("Is integer:", value)

with foo.strings as (string_1, string_2):
    print("Is strings:", string_1, string_2)

This example will end up printing out Is integer: 1

This opens up the possibility for matching values as well. Although not implemented yet I believe code such as this is possible to implement while keeping the current API intact.

@adt
class ContextMatching:
    NONE: Case
    OK: Case[int, float]

with foo:
    with foo.ok[:4, :] as (val_1, val_2):
        print("val_1 less than 4 and val_2 anything")
    
    with foo.ok[4:, 1.3:9.9] as (val_1, val_2):
        print("val_1 4 or higher and val_2 between 1.3 and 9.9")
    
    with foo.ok as (val_1, val_2):
        print("Unhandled ok cases")
@aarondewindt
Copy link
Author

aarondewindt commented Dec 20, 2020

Ok, so after a few months of having a look at this every once in awhile, I've decided to change directions. The main reason is because I'm not confident that the method I'm using to skip the code inside the context manager is reliable. It may fail depending on the interpreter (compile) setting, will probably mess with some debugger, and uses a function that is not part of the python standard and thus only guaranteed to be there for CPython.

So I've been trying to come up with new ideas for a nice looking match syntax and this is the best I've come up so far which I think won't require questionable workarounds.

@adt
class ContextMatching:
    EMPTY: Case
    INTEGER: Case[int]
    STRINGS: Case[str, str]

foo = ContextMatching.INTEGER(1)

with foo:
    if case(foo.empty):
        print("Is empty")

    if case(value := foo.integer):
        print("Is integer:", value)

    if case(x := foo.strings):
        string_1, string_2 = x
        print("Is strings:", string_1, string_2)

The main disadvantage of this, is that it requires python>=3.8. But I would make sure that the current API is still supported. So this feature would in the end only be available to those running on python 3.8 or higher. Is this an issue?

The walrus operator does not support unpacking, so you'll need to do the unpacking inside the if if it contains multiple values (strings), or use the result as a list/tuple.

The way I'm thinking of implementing this is by using the context manager to activate a "case access tracker", it would essentially throw an exception if one or more of the assessors where not accessed within the context manager. I would be implementing the descriptor protocol to track access to the assessors.

If accessing the case of the ADT, the accessor returns the value. If accessing the others, it returns a singleton object, lets call it Mismatch for now. If the case function receives the Mismatch object, it returns False. For anything else it returns True.

Since I'm using the context manager, the assessors will know whether they need to return the Mismatch object or throw an exception as it is with the current API.

I'll work on this during the holidays. Do any of have a suggestion or some feedback?

@jspahrsummers
Copy link
Owner

Wow, didn't realize you were working on this the whole time! Just to set expectations, I am not very active in maintaining this project--I think you are probably investing more than me at this point. 😅

That being said, I'm happy to support you in this, and can add you as a direct collaborator to the project. I think this proposal that you've laid out makes a lot of sense, and I have no qualms about restricting to Python 3.8+.

The one suggestion I would have is to make sure this is re-entrant (so caller and callee can both be pattern-matching at the same time, or a recursive function can have a pattern-match, or async/await code... etc). This is probably most relevant for the "case access tracker." If you would like help with this bit, I can try to offer some on a pull request.

Thanks for your contributions!

@aarondewindt
Copy link
Author

@jspahrsummers I think I might change to a completely different approach that takes advantage of the new pattern matching in Python 3.10, so I'm probably starting a new project. However I have the feeling I'll have to "borrow" some code from here, mainly the mypy plugin stuff which I don't have much experience with. Do you have any issues?

Here's a gist with what I already tried: https://gist.github.com/aarondewindt/1cb0af45f05c0da03bfbec6f050f5b58

@jspahrsummers
Copy link
Owner

Please feel free! This project is licensed under MIT to support things like that.

Repository owner deleted a comment from jeremigio2706 Mar 4, 2024
Repository owner deleted a comment from jeremigio2706 Mar 4, 2024
Repository owner deleted a comment Aug 23, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants
@jspahrsummers @aarondewindt and others