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

Can we have a nicer match syntax? #4

Open
radix opened this issue Feb 7, 2019 · 7 comments
Open

Can we have a nicer match syntax? #4

radix opened this issue Feb 7, 2019 · 7 comments

Comments

@radix
Copy link
Owner

radix commented Feb 7, 2019

How about:

with match(myobj):
    with case(MyClass.MyConstructor) as x:
        x.y
    with case(MyClass.MyOtherConstructor) as x:
        x.z

I think I could implement this, but I'm not sure how I would implement exhaustiveness checking.

@bergus
Copy link

bergus commented Feb 27, 2020

I guess if you implement a global stack to enable case know in which match statement it is located, you can also keep track of met cases inside the match contextmanager and throw on exit if not all possibilities were exhausted.

@radix
Copy link
Owner Author

radix commented Feb 28, 2020

looking back on this, this could also work, without any global stack:

with match(myobj) as M:
    with M.case(MyClass.MyConstructor) as x:
        return x.y
    with M.case(MyClass.MyOtherConstructor) as x:
        return x.z

@radix
Copy link
Owner Author

radix commented Jun 8, 2020

Actually, I don't think this is possible, because I don't think context managers have any way to stop their code block from being run.

@radix
Copy link
Owner Author

radix commented Aug 26, 2020

unless you commit crimes like what this PR for adt does: jspahrsummers/adt#38 -- basically using exceptions and python's ability to mutate the calling frame to jump around in the code.

@aarondewindt
Copy link

aarondewindt commented Dec 20, 2020

Hey hey hey, I'm pround of the atrocities I've commited in that PR, but I wouldn't recomend it though. My use of the sys.settrace function is questionable at best, will probably not do it's job in some rare cases and is guaranteed to mess up some debugger at one point. Unfortunately I've not been able to find some other way to block context managers from running their code, but I'll let you know if I find a better way.

I've been thinking of some other ideas though. If we're assuming python 3.8 or higher than something like this could be an option.

with object:
    if case(case_a_value := object.case_a):
        ...
    
    if case(case_b_value := object.case_b):
        ...
    
    if case(x := object.case_c):
    	case_c_1, case_c_2 = x
    	...

You'll still need to unpack multiple values inside the if because the walrus operator does not support unpacking (case c).

I don't think you would need a global stack, the context manager would put the object in a state where it would track access to the cases by using the descriptor protocol.

I think your solution with the decorator is the nicest looking, reliable and backwards compatible option I've seen so far. Do you mind if I experiment with it at adt? I'll give credit for the idea.

Edit: Removed the elif's, the object won't be able to check whether all cases where covered if the value isn't requested from every case.

@aarondewindt
Copy link

Maybe important to add. The case(...) function is needed because the object.case may contain a falsy value (eg, False, None, 0, etc). So my idea is to let the object.case return either the value or a Mismatch singleton object. The case function would then return True if it got a value or False if it got the Mismatch singleton. The value itself would already be in the variable thanks to the walrus operator.

@radix
Copy link
Owner Author

radix commented Dec 21, 2020

@aarondewindt Yes, you can of course feel free to take any ideas (or code) you want from sumtypes. I would honestly be happy to just deprecate sumtypes if adt can already do everything that sumtypes does.

One of the most annoying things I see with the decorator approach is that it's not really a match expression, it's a way of defining functions whose bodies are entirely match statements. So it's not that great for inline expressions, and also not great for one-off matches in the context of a larger function. Granted, the with syntax I discussed above also isn't an expression, but at least it's immediately executed.

The other most annoying thing about it is that using class is just ugly/confusing in general.

I guess it could also be possible to define a decorator which returns the result of immediately evaluating the cases.... but that would read very wierdly:

value = Enum.CaseA(1)

@match_immediate(value)
class foo_result:
    def CaseA(x): return x
    def CaseB(x, y): return y

assert foo_result == 1

yuck.

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