Add optional delimiters around match/try/function cases.#722
Add optional delimiters around match/try/function cases.#722yallop wants to merge 1 commit intoocaml:trunkfrom
Conversation
| | TRY ext_attributes seq_expr WITH opt_bar match_cases | ||
| | TRY ext_attributes seq_expr WITH match_cases | ||
| { mkexp_attrs (Pexp_try($3, List.rev $5)) $2 } | ||
| | TRY ext_attributes seq_expr WITH LBRACKET match_cases RBRACKET |
There was a problem hiding this comment.
This duplicate-rule style adds some amount of redundancy (for example in attributes handling). I suppose you tried defining a rule match_cases_with_optional_brackets or whatever and it introduced conflicts?
|
I... It feels weird to admit it, given that purely syntactic changes and they tend to generally be unconvincing in some way (except maybe |
|
I could try myself, but intuitively I would have thought that this introduced a conflict in the grammar since match e with [ A | B | ....could be the beginning of either your new construction or a standard list pattern. |
|
@alainfrisch: your example is parsed correctly: # type t = A | B | C;;
type t = A | B | C
# function [ A | B ] -> true | _ -> false;;
- : t list -> bool = <fun>
# function [ A | B -> true | _ -> false ];;
- : t -> bool = <fun>The change doesn't introduce any grammar conflicts (which cause build failures nowadays). |
|
@yallop: It's parsed correctly, but it is confusing. Not saying I'm against it, but there's definitely confusion with list syntax. |
|
Moving my question from #715: Is an (admittedly contrived) case like: simpler from a parsing perspective than the |
|
I'm much more in favor of this than #715 , but I think I'd personally still prefer |
|
@hcarty: indeed, that example is simpler, assuming that the opening |
|
Yeah, I prefer begin and end as @alainfrisch suggests... |
|
@yallop In this proposal the opening |
|
@hcarty: there's no problem with that example. The closing |
|
@yallop In that case I don't see the issue with the example you gave in #715 (comment). Is the issue that |
|
@hcarty: there's no ambiguity with The ambiguity arises when So making |
|
@yallop: Thank you, I think I properly understand your original concerns now. Apologies for the long list of questions - I appreciate your time on this. |
|
@alainfrisch, @mshinwell: there is, of course, nothing stopping you from continuing to use |
|
What about using |
|
@braibant: yes, I'm curious as to why you say you prefer Internal delimiters are quite harmonious with the rest of the language, e.g. with records, which we write like this: type t = {
x₁: t₁;
x₂: t₂;
}not like this: begin type t =
x₁: t₁;
x₂: t₂;
endand with modules, which we write like this: module M = struct
let v₁ = e₁
let v₂ = e₂
endnot like this: begin module M =
let v₁ = e₁
let v₂ = e₂
endand with a number of other constructs. Half-open sequences like It is possible that you prefer |
|
The point is the vertical alignment of opening and closing delimiters. For records, I tend to prefer: type t =
{
...
...
}and for structures: module M =
struct
...
...
endFor structures, it is true that I often write: module M = struct
endbecause it uses less indentation level, and vertical alignment is
For me, yes probably. But we are talking about adding a new form; the question is not such much whether it would have been better to have from day 1, but rather whether it is worth introducing it now. I find the existing form works well and I see no advantage in For me the problem is really not about |
I don't understand this point at all. This proposal precisely supports the style that you prefer: For records you write type t =
{
...
...
}For matches you can write match e with
[
...
...
]Of course, this style is not enforced, just as for records, modules, etc.
I'm in favour of your warning proposal, which solves a related but different problem. |
match e with
[
...
...
]wastes one more line than: begin match e with
...
...
end(and potentially one identation level as well, except if you align I could switch to a different syntax, even a bit heavier, if I could see any advantage. But I don't.
Your PR description states that the problem to be solved is a "kind of ambiguity in the syntax for nested matches". I don't see how the proposed solution improves on the current situation in this respect: people will have to know and think about the problem (and the warning will help), and if they do, they could as well use the existing solution. In the PR description, you also say that the existing solution "is a little heavy". This is a matter of taste, but I find: begin match e with
...
...
endless heavy than: match e with
[
...
...
](or the equivalent with match e with [
...
...
]but it is actually more difficult to type on an AZERTY keyboard and it lacks the vertical alignment of the "straightforward" solution; moreover it's not a lot lighter than the current solution. Nothing to justify for me to use another syntax. (And the small advantage over the current solution in term of lightness disappear if you use But again, I'm not against the proposal. I don't see any advantage over the existing solution but several other expert people do; syntax is rather subjective and I can live with the fact that I don't understand the benefits of the proposal. |
|
Frankly, I really don't get this. I'm not a fan of Which is valid code in current OCaml, and solves already 90% of the mentioned stylistic problems. Please, please, introduce new types of parentheses only if they really improve something. (Off topic: I have a suggestion, somewhat borrowed from Scala, namely |
|
I also don't see why But, I do think the Edit: Sorry, only read #715 after making this comment. That PR is more or less the same idea as the following. A better syntax in my mind would be a consistent revision to use and eventually drop the This seems tricky to change without breaking existing code, though. |
It's more than tricky; it's (probably) impossible. Without a concrete, backwards-compatible proposal, what you describe isn't a "better syntax" in any practically useful sense. |
Of course, you're right. It's a different syntax. I was merely considering if the same parser could handle current syntax as well for backwards compatibility. That may be impossible too, if the ambiguities cannot be resolved, but probably would require too much backtracking anyway. "Better syntax" OTOH is a matter of taste. |
|
A reason I wasn't thrilled about this PR, was that I could not find a fully satisfactory way to indent the code, as the snippets I converted just seemed noisy. I played around a bit more this weekend, and I've put up one of the functions formatted in different ways comparing this PR compared to the closest parenthesised analogue: https://gist.github.com/paurkedal/3526cdf7cf65baea271d40f42fd50392 My own favourites are the ones labelled OLB and OCP (and well, maybe the esoteric OXX). OLB uses brackets and and OCP uses parentheses, both indenting patterns on odd columns, which I haven't even considered before. So, at least I'm assured that there are reasonably tidy ways of formatting code using the syntax suggested by this PR. On the other hand, I think I'll stop writing On a different note, would this be allowed:? List.exists
function [
| None -> false
| Some _ -> true
]
xsA future possibility would be if List.exists fun[None -> false | Some _ -> true] xs |
|
@paurkedal If we're in the esoteric proposals, what about the following variant of OXX, which does not require any extension to the syntax, and is 99.99% compatible. The idea is that a wild card pattern closes the pattern matching syntactically. This is of course already the case semantically, as all cases after it are unreachable by definition. The interesting thing is that the introduction of refutation cases allows to add a wild card to any pattern-matching, while preserving the exhaustiveness check. It is not 100% compatible, at it could change the extent of some pattern-matchings, but these are clearly meaningless ones, and the user would have been warned. IIRC, automatic indentation shouldn't be too tricky either. The parsing might be the hardest part. |
|
@garrigue: one tricky situation is where exception cases are involved, since exception cases can legitimately follow a wildcard: match h () with
| g ->
match g () with
None -> ()
| _ -> .
| exception _ -> ()@paurkedal: indeed, easing future changes to the syntax is a major motivation for this proposal. |
|
@garrigue Yes, though isn't match-all patterns less common than proper exhaustive patterns? At least I would hope so, since that's one thing which makes refactoring safe in OCaml. I think OXX would be 100 % compatible as long as pointing out the last case is optional. In fact, introducing new keyword to optionally terminate patters would be backwards compatible except for the introduction of the keyword. |
|
A case for this PR which was not included in the above gist is where the keywords of the construct is spread over multiple lines, as in match
a_long_expression
with [
| Ok () -> ()
| Error -> exit 69
]In the parenthesised version, either This issue arise even as the common case with So, I'm convinced now that this PR is better than the status quo. PR #715 seems more beginner friendly, though with a bit more sophisticated indentation rules it seems we can write at least as readable code with this. |
|
@paurkedal Why don't you just let-bind the scrutinee of the I'm not sure that support for this proposal is at the level it would need to be for this kind of change. (I basically agree with @alainfrisch , FWIW.) What do people think of @garrigue 's alternative? @xavierleroy Do you have an opinion? |
|
Are there ideas for @yallop's concern about exception clauses in @garrigue's idea? I guess it boils down to how much code 'in the wild' this may break? There's something about forcing the wildcard pattern (with refutation, as appropriate) that I find appealing, since it forces a 'future warning' (e.g. because of a new constructor added to a variant) to become an error. Presumably it also means that extra clauses after the wildcard clause also become errors rather than unreachable warnings? |
My view is that it's more a question of design. A change that doesn't break any existing code at all can still break conceptual coherence. @garrigue's suggestion works well if we ignore exception patterns. But with exception patterns it results in quite surprising behaviour. For example, in the following code the exception clause belongs to the inner match d with
| x -> match e with
| A, A -> x
| _, _ -> .
| exception (E y) -> yAt present the following code is entirely equivalent, but with wildcards-as-terminators the meaning would change so that the exception clause would belong to the outer match d with
| x -> match e with
| A, A -> x
| _ -> .
| exception (E y) -> ySo reusing top-level wildcard patterns as terminators introduces a discontinuity in the design that could be pretty surprising in practice. |
|
@mshinwell One potential issue with pulling the scrutinee into a let binding is exceptions. An exception clause couldn't be used unless the expression were wrapped in a thunk or made lazy. |
|
My idea was only a hack to start with, @yallop 's comment basically killed it, so I didn't bother to answer at the time. Sorry, there does not seem to be any miracle solution. |
@mshinwell Yes, and I think try-catch isn't that big a deal as it's a less common construct and often does not interfere. So this was just luke warm support for []-delimited patterns from me. I think current syntax with parentheses and 1-space indentation of patterns works just as well most of the time. |
|
@yallop - fair enough, I was wondering how damaging it would be to require that exception clauses must appear before wildcard clauses (the "in the wild" part) and personally found the potential confusion were that forgotten to be no worse than the FWIW, I like your original idea too - although I sort of agree with Gerd's comment and would actually prefer parentheses (or even |
|
Parentheses do have the following advantage. What @yallop is doing is giving a first-class syntactical status to clause sets, which is a family of |
|
We discussed this PR at the developer meeting two days ago and the consensus was to work on #716. When that is merged, then this PR is too small of an improvement because you can simply add |
testsuite: re-enable signals_alloc testcase
[Since there was some support for this suggestion (thanks @alainfrisch, @gasche, @dbuenzli, @bobzhang!) under #715, I've opened a PR for further discussion.]
This PR adds optional delimiters around the cases in
try,match, andfunctionexpressions. As an alternative to the current syntax:this change gives you the option of writing your code like this:
What problem does this solve?
There is currently a kind of ambiguity in the syntax for nested matches. In the following example, the case for
C₂actually belongs to the innermatch, despite the indentation:Of course, this is not strictly an ambiguity in the grammar. But it is a potential pitfall. Fortunately, there is a straightforward workaround --- you can surround inner matches with
beginandendor with parentheses:But this is a little heavy, and suggests a shortcoming in the current syntax, since the delimiters go around the
matchexpression; they are not part of the expression itself.With the new syntax the delimiters are part of the syntax of the expression, and the nested
matchmay be written like this:A warning along the lines of @alainfrisch's proposal in #716 for nested
matchexpressions without delimiters would work well with this change as an additional help for avoiding the problem.What about the optional initial
|?Following @gasche's suggestion, this change supports an optional
|before the first case:This has the advantage of making the cases easier to edit, since there is no special initial case.
What should the delimiters be?
This change currently uses brackets (
[...]) to delimit cases. But the syntax would equally well support parentheses ((...)) or even braces ({...}). On balance I think the arguments are in favour of brackets.Advantages of brackets
Advantages of alternative delimiters
(|is not a token, so there is no need for a space between(and the optional opening|.switchstatement in C-family languages, and from Reason.