Open abelbraaksma opened 1 year ago
Wouldn't it be impossible to perform an exhaustiveness check? I don't like the improved sample, because it's confusing that it takes no parameter and basically acts as a catch all branch, which defeats the point of the match in some ways, that is at first glance.
You can do this, even if it is more verbose and has overhead (unless we inline it all):
let comp x y funGe funLt =
if y >= x then funGe ()
else funLt ()
let f() =
comp 42 12 (fun () -> "greater or equal") (fun () -> "smaller")
Well, the example is just that, an example. Certainly there are other ways of writing that particular code. What I’m looking for is arguments to a total (exhaustive) pattern.
Maybe we should drop exhaustiveness, and allow for multi case partial patterns (which, in single case partial pattern scenarios, already allow for arguments, but that leads to repetitive code and suboptimal generated code).
Note that in the text above I do touch on a way to make the exhaustiveness check work with this sort of pattern.
Yeah, I suppose it could be exhaustive if it's just the first case that takes the pattern. However, then you can a DU and move the parameter up:
type GeLt =
| Ge
| Lt
let comp x y =
if y >= x then Ge
else Lt
let f() =
match comp 42 12 with
| Ge -> "greater or equal"
| Lt -> "smaller"
That's still not an active pattern but has the same ergonomics.
allow for multi case partial patterns
That crossed my mind too, you have to keep executing the active pattern body for each consecutive branch with a different parameter though:
let (|G|L|_|) x y =
if y > x then
Some G
elif y < x then
Some L
else
None
let f() =
match 42 with
| G 500 -> "greater than 500"
| G 200 -> "greater than 200"
| G 50 -> "greater than 50"
| L 12-> "less than 12"
| _ -> "something else"
This can be done today with 2 active patterns. What I am trying to say is that not everything has to be solved by an active pattern if a similarly straightforward alternative exists.
Maaaaybe this could work?
let (|Ge|Lt|) x y =
if y >= x then Ge
else Lt
let f() =
match 42 with
| Ge 12 -> "greater or equal 12"
| Lt 0 -> "less than 0"
| Ge 5 -> "greater or equal 5"
| Lt 5 -> "less than 5"
As long as the the very last sequence of branches goes through every active pattern case with the same parameter, the compiler could conclude the match is exhaustive.
We always intended to do this, but the reliance on a choice type encoding held us back, plus the general complexity compared to number of use cases. The metadata allows for it in principle.
I guess that qualifies as an approve in principle
Tx @dsyme! I’ll work on an RFC. I think the main thing to decide upon will be whether a single argument should be provided (that is, only the first of the cases), or we go the more advanced route and compiler will consider same arguments in the ‘totality check’ (that is: each case with same arg translates to single execution of active pattern).
If then different args are used, unless the totality check concludes that at least once all cases with same arg were used, the normal warning for incomplete pattern matches is raised.
This (more complex) approach has my preference as it gives more freedom to consumers, but may lead to significantly more work to implement.
PS: I’m not sure if the expression forming the argument in the match cases could potentially be written with a side effect. If so, the totality check should only apply to constants (as args) and/or idents.
Edit: yes, this is currently possible, ie, the argument could be a function item, which in turn could execute a side effect. However, it may not matter if the active pattern code is executed once. So, in short, totality check is still possible.
Just happened upon my own bug report again, lol. Apparently I try to use this more often than I care to admit. Hmm, time for an RFC, I guess.
Anyway, I found a clumsy workaround. Apparently, you can use active patterns with a variable in the match clause. It does require both of the x
's to be the same, i.e. match x with
and MatchLower x ...
must both be there.
This seems like a bug (or a feature?) and something to keep in mind that this might be used in the wild:
[<AutoOpen>]
module StringPatterns =
/// Matches a string by its lowercase invariant version against the given constant string
let (|MatchLower|_|) value matchValue =
if String.trimToLower value = matchValue then
Some matchValue
else
None
/// Matches a string by its uppercase invariant version against the given constant string
let (|MatchUpper|_|) value matchValue =
if String.trimToUpper value = matchValue then
Some matchValue
else
None
let compareString x =
match x with
| MatchLower x "foo" -> true
| _ -> false
Funny that this is always true??
let compareString x y =
match x with
| MatchLower x y -> true
| _ -> false
This question came up a few times recently on Slack, which suggests to me there's a real-world use case for this scenario. This active SO question is another cry for this feature.
I propose we extend support of parameterized active patterns to include total active patterns, where the pattern has multiple choices.
There's currently no existing way of doing this in F#, such patterns are explicitly forbidden.
Example
Syntax
As the above example shows, one might think that writing
Lt 11
would make the pattern match non-total. This is correct, and honestly I don't have a good way around it. Since a total active pattern is considered a single block, perhaps the rule could be that only the first item should have the argument. After all, that's how such patterns are compiled currently:Improved example
Pros and Cons
The advantages of making this adjustment to F# are
The disadvantages of making this adjustment to F# are
Extra information
Estimated cost (XS, S, M, L, XL, XXL): S
Related suggestions: I couldn't find any, though I'd be surprised if this wasn't suggested before.
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.