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
Or patterns #43
Or patterns #43
Conversation
I really like this idea and often wanted to have something like this. I assume an or-pattern where different parts reference a different set of variables or the variables have different types will just result in an error message? |
That's what I thought, but I think if we do something like:
then we can allow different types for same variables in different patterns. Example: data T a = C1 Int | C2 Bool
f :: T a -> String
f (C1 x | C2 x) = show x If we type check f a = case a of
C1 x -> show x
C2 x -> show x To keep things simple (and in within the parts of the compiler internals that I understand), I said "same set of variables of same types". We can either generalize this later or right now if type checker experts can chime in here. One thing to note here is that is we restrict patterns to bind same set of variables of same types I think desugaring gets a lot simpler: we just generate a local function for the RHS, and call it with same set of variables in each case (similar to how |
One alternative to this feature is simply a warning for the existence of a wildcard pattern match. |
What would be the fix for that warning? Without or patterns you just replace one problem with another (namely, wildcard patterns with repetitive and/or duplicated code). |
That's true, but (IMO) it's replacing a big problem (wildcard patterns) with a small(er) one (duplicating the RHS on all the patterns you want to treat uniformly). |
I would very happy to have these. Ocaml has or-patterns already (https://caml.inria.fr/pub/docs/manual-ocaml/patterns.html#sec108), so we could borrow ideas from its implementation. A real-world use case where or-patterns would be helpful that I mentioned just the other day to someone working on improving iOS support in GHC: picCCOpts :: DynFlags -> [String]
picCCOpts dflags
= case platformOS (targetPlatform dflags) of
OSDarwin
-- Apple prefers to do things the other way round.
-- PIC is on by default.
-- -mdynamic-no-pic:
-- Turn off PIC code generation.
-- -fno-common:
-- Don't generate "common" symbols - these are unwanted
-- in dynamic libraries.
| gopt Opt_PIC dflags -> ["-fno-common", "-U__PIC__", "-D__PIC__"]
| otherwise -> ["-mdynamic-no-pic"]
OSMinGW32 -- no -fPIC for Windows
| -- ... The As for syntax, I'm +1 on using As for semantics when binding variables of different types, matching on existential constructors, etc., I'd be content with an incremental approach of supporting the simple cases first; that's where most of the value is anyways. |
We should have done this along time ago. I use it all the time in Rust too, and miss it all the time in Haskell. One interesting thing to note is that exhaustive lazy or patterns make sense. |
Can I use it in
like this? |
Yes although in this situation you would need parentheses around An or-pattern is again a pattern so it can appear wherever a pattern can, including for example nested within another pattern ( |
@vagarenko, like @rwbarton said, an or pattern can appear anywhere that a pattern can appear. OCaml already supports or patterns in full generality (i.e. they can appear anywhere that patterns can appear). This is from Real World OCaml: let is_ocaml_source s =
match String.rsplit2 s ~on:'.' with
| Some (_,("ml"|"mli")) -> true
| _ -> false In Rust this is not the case, the reference says "Multiple match patterns may be joined with the | operator.". I started thinking about the implementation. As the first thing I think we may have to make significant changes in the parser. Currently patterns are subsets of expressions, so we have productions like this:
With this change patterns won't be a subset of expressions, so we may want to first parse for a pattern, and then try to transform it into an expression. Does anyone have any other ideas on this? |
Pattern syntax was never really a subset of expression syntax (especially before TypeApplications):
I'm not sure whether the pattern parser reuses the expression parser out of technical necessity (e.g., we don't know up front whether we are parsing a pattern or an expression) or out of convenience. If it's the latter it might be time to create a separate pattern parser. IIRC the reuse of the expression parser already causes some oddities around the precedence of |
It's out of necessity. If a line begins Naked top-level splices are a misfeature, in my opinion. |
Is this proposal to force the pattern-match to match every possible ADT value explicitly? There are times where just having a wildcard Actual use-case -- we start with the following ADT definition and call-sites:
Now, assume that within Another one: assume that within Therefore, my question. Can we add a pragma to ADTs to disallow wildcards? eg.
Once this is done, having the |
Thanks for the examples @saurabhnanda. Your examples are exactly the same as the second example I gave in the proposal. About the pragma: it sounds like a good idea, but it's orthogonal to this proposal and can be done separately. Even without or patterns it may be useful, so I suggest creating a new proposal for that. So no, this is not a proposal to force pattern matching on every possible ADT constructor. |
@osa1 I'm not confident of being able to write all these sections: https://github.com/ghc-proposals/ghc-proposals/blob/master/proposal-submission.rst#content-of-the-proposal-document |
I don't really see the value of
or in case of non-enums with I.e. at the end it will be about discipline in the team, so I don't see OTOH, feel free to experiment with writing |
It wouldn't call a programmer doing that "lazy". It's more like an hostile attacker actively trying to break the rules. Now, it's unfortunate that this issue exists, but it doesn't take away from the usefulness of |
I'm not against or-patterns, even mildly in favour. But
Let's NOT do this . It would add a huge amount of complexity to a basically-simple feature. Each pattern in an or-group should bind the same term variables, exisitential type variables, and constraints. Don't forget there is work to do to fix up the pattern-match overlap checker. |
starting from some level of complexity the good complex rule that is catch all but it may be changed in future, and workaround for bypassing the wildcard restriction feature are indistinguishable.
Then with the change of the data type - the type of deconstructor will also change and all users will be notified. |
it was a necessity even before top-level naked splices, e.g. in the statements of |
Why? An error message “ |
The section “Interaction with other extensions” should discuss the interaction with
It may be that a desugaring with view patterns will not be able to answer or express these questions, as we cannot return a type variable in the |
Another language extension interaction worth discussing:
Currently, the proposal wording accepts I am currently inclined to think that this is unnecessary strict, and am wondering if it would not be nicer if it accepts
Of course the proposal ought not specify the wording of error messages, this is just for illustration. This is related to my earlier point about
One might argue that the first line is bad style (and I agree). But note that even if the proposal allows the former, then the usual “unused bindings warning mechanism” will ensure that |
I had a long nice walk with Richard and we talked about possible ambitious ways of approaching or patterns (what if types are not equal, but subtypes? what about existential variables? what about constraints). We had many intriguing ideas, but nothing that immediately clicked or solved all the problems… I hope that one day we figure out how to make or-pattern so strong that for all patterns I would still like to discuss the (not very technical, and more bikesheddingly) question of whether it is a compile error if some unused variables don’t meet this requirement. |
It's interesting how much is hidden inside such an apparently small innovation as or-patterns! I'm still liking the spec "if the desugaring to view patterns typechecks, so does the or-pattern", because it is so simple and explicable. I'd love to see a direct typing rule for this; I don't think it should be too hard. Unused variables. A variant of the design, which Joachim is implicitly suggesting, is to say that I quite like that idea:
So that change seems like a modest win to me. The only downside is that given
you might just get "x is not in scope" (as indeed it isn't) and be puzzled. But of course extra work on the renamer could produce a more informative error message. Scoped type variables I can see that the specify-via-view-patterns story does not allow any scoped type variables to be brought into scope, even if they don't involve existentials. That's a shortcoming. To me that's the strongest argument for a more direct typing rule. But unless someone wants to do that work soon, and it happens to work out really smoothly, I don't think we should let it stand in the way of doing something simpler for now. |
For reference, I have written the moral equivalent of this proposal for Rust, rust-lang/rfcs#2535. |
https://mail.haskell.org/pipermail/ghc-steering-committee/2018-June/000646.html
|
As said above this is not ready for merging yet. There are still a lot of things to specify. |
The equivalent Rust proposal, rust-lang/rfcs#2535, has now been accepted. If you wish, you can make a note of this in the subsection on Rust. I'd love to see this happen in Haskell as well. :) |
Thanks for the update. Updated the proposal. |
I don't have time and motivation to work on this project anymore, so closing. The hard part is defining semantics of current syntax before adding new to it. Once the semantics is there this should be a simple addition with a few tricky cases for scoped type variables and view patterns. Until then author of a proposal like this needs to define the entire language which is just too much work. |
Perhaps it makes sense to turn this work into a SoC project. I submitted haskell-org/summer-of-haskell#84 for this. As usual feedback is welcome. |
Shouldn't it be re-opened because of haskell-org/summer-of-haskell#84 (comment) ? |
Well, it needs an active champion. Anyone who wants to play that role can of course reopen it. |
I'd very much love to see this supported in GHC. It'd make the life of the "working industry programmer" significantly better. If it helps simplify design/implementation, you can outlaw binding any parameters in the "or" case. I.e., you can pattern-match multiple constructors, so long as no variables are bound in any of those matches. This is a simplification, and I doubt it would take away much from usability in practice, yet deliver a nice solution to practical problems. (Of course, if it doesn't add extra complication, do allow them; but it's an option perhaps for the first version of an implementation?) |
Yes; perhaps we can deliver on nested (field) matches in a future proposal. That is, I propose the following change: -p1 and p2 bind same set of variables.
+p1 and p2 bind no variables, constraints or dictionaries. In particular, this change entails not having to worry about GADTs and whatnot for now. (No type refinement of the pattern variable is possible either.) I believe that if there is a semantics for or-patterns that can cover GADTs and bound variables, then this semantics can be expressed as a backwards-compatible extension to this simpler semantics. I think we are rather late to the party:
Let's ship the MVP that catches 90% and think about the hard 10% case afterwards. @LeventErkok (or someone who reads this!) would you be up to recycle this proposal into a new one that delivers the MVP? |
When you say
Do you mean that the patterns must not bind any of these, or that any bindings are ignored? For variables probably the former (i.e. requiring the programmer to write Here is an example. Allowed or not?
|
The latter, at least in Haskell. The former in the desugaring to Core (where it's all just variables, I guess). I should have been more clear. So your data U a where
MkInt :: U Int
MkInt2 :: U Int
bar :: U a -> a
bar (MkInt | MkInt2) = 42 :: Int That is, an or pattern will never provide new Givens such as Perhaps it should just be -p1 and p2 bind same set of variables.
+p1 and p2 bind no variables. because the proposal already states, in 1.4.1
Maybe that section should be reworded in terms of "Givens"? Not sure that helps. Anyway, my changed proposal would allow GADTs in or patterns (including existentials), but the desugared Core would not bind any of the aforementioned GADTy stuff in the view pattern. NB: A future proposal could relax this requirement and (semantically) allow more (syntactically valid) programs in the process that would be semantically incorrect in the MVP. I believe that is what the current proposal aimed at and is also what caused it to come to a halt, even after it only tried to focus on simple field binders without GADTy stuff. The details are non-trivial, especially if we want to keep the desugaring to view patterns. |
Thanks guys clarifying! |
? I think this proposal is in the same design space as the As at today, I think you could write a Pattern Synonym/ViewPattern to give a uniform view over diverse constructors -- again with more flexibility of typing. Admittedly that comes at cost of a couple of extra declarations, and some rather clunky code. |
@AntC2 the interaction of Or-patterns with f p1 q1 r1 = ...
f p2 q2 r2 = ... I think in both cases (i.e. here and with |
Note that #522 is an evolution of this proposal that eschews variable bindings altogether (in a forward compatible way), as motivated by #43 (comment). Feel free to offer your opinion there, since this proposal is dead as far as I can tell. |
Rendered
Trac ticket
/r/haskell thread