-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
RFC/AISlop: match statement and declared exceptions
#60344
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
base: master
Are you sure you want to change the base?
Conversation
|
I am not a heavy user, but I think https://github.com/Roger-luo/Moshi.jl has a pretty well-thought-out design for reference |
|
There's several pattern matching macros in the ecosystem. I've taken a look at a few of them, but this doesn't match any of them exactly - you have more freedom in syntax, but there also needs to be more uniformity with the rest of the syntax, so it works a little different. |
|
Capturing some discussion points from this morning:
One possible solution is to make i.e. the following are equivalent There's a little bit of a question of what to do if you have both: I think the answer is that you need to satisfy both |
|
I like this design a lot. I like that
I have some questions about the design
Does the
Does
|
Returns from the closure, but the more interesting question is if you annotate an exception type on the outer function, does it apply to the closure. I haven't fully thought this through yet.
Returns 0.
Yes, you can access the inner function directly. The implementation in this PR is not quite correct yet in this regard, so I can't give you the syntax for it to try out, but it'll be something like: (same as |
I'm leaning towards no. |
|
I had a bunch of thoughts on this that maybe superficial or a bit bikeshed-y at this stage, but I put them in a gist in case it sparks some thoughts or is useful in future discussion: https://gist.github.com/digital-carver/be6a16b9d3d9d4faa3fbb82ee0054feb |
In general, I appreciate hearing everyone's perspective - it's very easy to get attached to a particular design, so hearing input is helpful. That said, I will also caution that the same attachment can also happen for people commenting on designs, so if anybody makes a suggestion that I end up not taking up, please don't take it personally :).
The mechanism is not orthogonal, is is a locally structured extension. If you do not handle a declared exception, it gets automatically thrown, so you would expect the type hierarchy of declared exceptions to be a sub-hierarchy of the things you expect to be thrown. I do agree that there needs to be a distinction drawn between the mechanisms, which is why I didn't go with the
I was considering this, with various variations on using the ASCII
I think there's a misunderstanding somewhere. |
|
to bikeshed the term |
|
Claude informs me that we're reinventing modula-3, which both uses the |
|
Modula 3 uses |
|
Claude suggests which is kinda cute (and shorter than |
|
|
|
Re: Consider an example from your exceptions design document: The problem here of course is that an array comprehension creates an implicit closure, which the I don't have a solution - it would be too weird and unmanageable if |
|
Well the problem in general is that there isn't really a guarantee that the outer function is still on the stack. That said, for comprehensions, the closure is a bit of an implementation details, so we could maybe treat that specially. I do agree that it deserves careful thought. |
I'm probably having a slow start to my day today, but what does this mean? Are you saying that the implicit closure function from the generator is no longer in the call tree, or that Some other points, mostly bikeshedding since the general concept of pattern matching is well established in other languages by now:
Overall, it feels like the current state of this is mostly geared towards making error handling easier, rather than a general-purpose match expression. If that's the goal, why not add something like |
I'm saying that in general, we cannot be guaranteed that the function that creates a closure is on the call stack, so we cannot necessarily return from it, the semantics needs to make sense independently.
It would not. As I said above
It suppresses the exceptional default case. It avoids having to invent a name just for this purpose.
It could if somebody wanted to add additional static analysis to julia, but this is not a design objective at this point
Works like
The syntax does not care and is extensible. As for defaults provided in Base, the natural extensions to property destructuring are intended to be supported. I have not thought about indexing.
No, they are separate proposals, with
Because the whole point is that it's not |
|
Here's my comments on the match proposal specifically. I think this is less great than the exception idea. Most importantly, I think the proposal, as-is, provides very little value over an if-else chain. Therefore, the proposal's new syntax doesn't carry its own weight. Where the proposal gives value is by enabling improved destructuring, and so the additional control-flow mechanism, identical to the existing if-else, is not a meaningful new feature. That is, I don't know why I would use a match instead of an if-else, and so the feature seems like a pointless TIMTOWDI - but maybe I'm missing something? In other languages, a match statement differentiates itself from an if chain by being exhaustive. This might be motivation to have this. Some less important points of objection / questions:
|
|
Some jumbled thoughts: Declared exceptions
function f(x) may A
g(x) may B
endwe should treat this how we treat type assertions, i.e. first we apply the Match
I worry this might end up being a big fragile and lead to weird bugs. Honestly, I kinda think that this should just be This |
|
Thank you for your extensive reply! I don't have time to respond to every point right now, but I'll get to it by the end of the week.
I think I understand now - do you mean an example like this: Where the return cannot be from |
Honestly, I've always thought so as well, which is why I thought it didn't really make sense standalone, but in the couple days of using vs just feel so much nicer even without any destructuring.
It does, see above.
I'm thinking about using
This wasn't originally part of the proposal, but was added based on the experience of using it and writing this default case all over the place, which felt very annoying. Not set on it, but I did find it a nice ease-of-use improvement. |
Yes
Possibly yes, but two points:
though of course, the difference here is that there is an existing
I think if we start treating it seriously and using it everywhere in base, it deserves to be syntax. You can certainly make a macro version work (which is what several packages already do), but the constraints of existing syntax make it look quite clunky in several cases.
The rhs of match arms all have normal semantics. Think of this as the identifier list in: |
Yes |
this seems like a quite nice idea to me thoughts on |
|
Regarding the match proposal, having val = match (a, b)
(1, n) -> begin
if n == 2
break 1
end
2
end
_ -> 3
endOr just skip it and require folks write an expression or factor out a function if they want to exit the match without exiting the function its in: # use an expression
val = match (a, b)
(1, n) -> begin
n == 2 ? 1 : 2
end
_ -> 3
end
# or use a helper
function helper(n)
if n == 2
return 1
end
return 2
end
val = match (a, b)
(1, n) -> helper(n)
_ -> 3
end
edit: I see break with value was just proposed, I missed that! |
I think this makes sense if it uses |
The facility for |
it wouldn't "break" so much as "be super confusing" I guess? since right-associativity of |
Sure, maybe that's not so bad: |
Pattern matching featuresMLStyle.jl's pattern matching by @thautwarm & @Roger-luo has nice features that I would like to have in built-in pattern-matching syntax. A few in particular come to mind: quote patterns julia> @match 2 begin
$(1 + 1) => "two"
end
"two"c = ...
@match (x, y) begin
(&c, _) => "x equals to c!"
(_, &c) => "y equals to c!"
_ => "none of x and y equal to c"
end# 1-ary deconstruction: return Union{Some{T}, Nothing}
@active LessThan0(x) begin
if x >= 0
nothing
else
Some(x)
end
end
@match -15 begin
LessThan0(a) => a
_ => 0
end # -15I would also like exhaustive matching for enums, with footgun protection: @enum FRUIT Apple Banana Pear
@match f begin
apple => 1
Banana => 2
Pear => 3
endMistyping |
supported Not implemented, but I like Supported via
As implemented, these need to all be |
|
Random thoughts on declared exceptions:
|
Yes, I agree that
We're taking a look at #36628 again to see if we can form a view. Thoughts welcome over there. Postfix
Yes, that's the whole point, otherwise you might as well just use try/catch. You want to make sure that your error handler only applies to exceptions it expects to and if somebody else has an exception that happens to be the same type, you get an error. |
Is the issue that if the following did automatically propagate, you might have no idea where the error came from, so it is better to only opt in once you're sure you've handled everything? |
|
Yes, but maybe ErrorException is not evocative enough. Imagine something like this: You don't want to propagate a BoundsError if there's a bug in the implementation of |
This is a fairly chunky syntax PR that implements two new features that are technically unrelated, but I would like to propose together:
matchcontrol flow statement (previously discussed in A case/switch statement for Julia #18285)The semantics for match are described in https://gist.github.com/Keno/e08874a423e0f1df4d11a798c7ed147c.
The full semantics for declared exceptions are described in https://gist.github.com/Keno/7d1fb27a003afba6de50686ccfbb6610
However, the basic gist is that you can annotate expected exception types together with return types
then, there is a new postfix
?operator that propagates declared (and only declared) exceptions:The semantics of postfix
?are to propagate any declared exceptions, otherwise they getthrown. To more selectively treat exceptions there is a newmatch?control-flow structure, with the same semantics asmatchdescribed in the link above, but operating on the declared exceptions only (if in the exceptional path). Example:This PR is semi-functional, but entirely AI slop - do not read the implementation, you have been warned. The purpose of this PR is to provide a dummy implementation that can be used to feel out the design ergonomics and discover any unexpected corner cases.