-
Notifications
You must be signed in to change notification settings - Fork 21
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
attribute enforcing explicit matching of DU cases #731
Comments
Great idea! |
Another con to this is that you can achieve this today by setting warnings as errors, though I can see someone wanting compile errors for failing to be exhaustive while not wanting an error on all warnings. Some other considerations:
I'm in favor of this in theory, though I'd probably have to think about it more |
Actually, I'm not able to achieve this today, if matches use wildcards, adding a case will result in warnings (that are great) in places there are no wildcards. The design of that optional restriction feels similar to
Agreed, this is really a feature to allow optional strictness on specific DUs, and I don't believe it applies to general libraries, but more to domain specific code; where having some extra explicitness on how matching should be done for selected entities seems like a win / great tool.
Agreed, there would probably be need to enrich few places in the documentation, language specification, guildelines, maybe most of it could remain concentrated on the attribute documentation, and would go along well with existing content touching on Thanks for feedback guys! |
What about nested pattern matching on these types? Consider these examples: [<EnforceTotalMatch>]
type StrictDU = Enforced1 | Enforced2 | Enforced3
let strict = Enforced1
match Some strict with
| None -> "nothing"
| Some _ -> "something" // error?
match [ strict ] with
| [] -> "empty"
| _ -> "not empty" // error? If the error happens for nested patterns, then I think it would be too impractical to use. If it doesn't happen on nested patterns, then the restriction quite easy to work around, limiting the usefulness of the feature. Maybe it would only actually be enforced if at least one case was explicitly mentioned? |
@theprash great question! I'm eager to see what other think about potential issues / inconsistencies that would make the feature useless or if it can be made sound / respecting the principle of least surprise for most F# users: In both of the matches you shown, if the type of If the compiler knows that The second match is ambiguous and it would definitely be more expensive for the compiler to figure out if a wildcard is OK or should fail than it is now; since the second match you are using is not binding the DU instance at all, I think that one should succeed the compilation.
I agree that the second example is showing the kind of edge cases that would need to be nailed down, and more consistency/use cases considerations required for a RFC.
I don't think it should be let to happen; so long a |
I think this can be implemented consistently if, for the purpose of warnings or compilation, So in @theprash 's example, match Some strict with
| None -> "nothing"
| Some _ -> "something" // Error: _ is not allowed to match a StrictDU
match [ strict ] with
| [] -> "empty"
| _ -> "not empty" // OK: _ is allowed to match a List<StrictDU> because List<_> does not have [<EnforceTotalMatch>] In codebases that you control, I think advice to minimize use of |
I think there's a big difference between a match Some strict with
| None -> "nothing"
| Some _ -> "something" // matches everything and this: match Some strict with
| None -> "nothing"
| Some Enforced1 -> "something1"
| Some _ -> "something2" // matches remaining cases To me, the first case is just a general ignore and should not cause an error. The second one is the case where an error would be useful. |
Great discussion. Thinking about this at a bit more of a high level, there are two axes by which to grade a feature like this:
I'm curious how folks here feel about @charlesroddie's example and @Tarmil's example on these two points. |
Not sure why exhaustive match is not always preferred? This is the approach rust goes with, if match is used as an expression. I dislike sprinkling the language with attributes here and there. |
@Swoorup Exhaustive match is always preferred: F# gives a warning if a match isn't exhaustive. But this is specifically about failing when exhaustive match was achieved using a enum MyEnum {
Case1,
Case2,
}
match myEnum {
Case1 => 1,
_ => 2, // No problem
} |
That implementation detail doesn't impact at all the consumption side, so it is a minor concern to me. If you like the suggestion, what would you do to mark the DU you want it to apply on? I get used to the name of attributes I find useful in my design, and I prefer that to keyword soups for this type of features. (it is also much easier to not have to change the grammar and carry in the type checker for a apprentice compiler maintainer) @Tarmil: do you feel there is a tension between #222, the pre #222 defaults of F# and this suggestion? |
I'm marking this as approved in principle. It's a great addition to allow enforcement of increased soundness of consuming code |
The safety around exhaustive pattern matching is one of the strong point of F# type system,
For F# users, discarding some cases of a DU in a match is always a fine balance between future changes and state of the code using the DU.
I'm sure many F# users face situations while adding cases to the DU or reviewing / adjusting the existing matches or those to add asking self:
In specific cases, it would be relevant to make usage of the wildcard discouraged more than morally, but with the tools of software engineers: the compiler.
sample
What:
Why:
How To Fix:
Where:
NB: An error is produced opposed to the warning we usually get if there is no wildcard and missing cases.
Usage in other conditional constructs is an area of investigation / suggestions, maybe usage outside
match
andfunction
should lead to a new warning.This could be refined to apply to individual cases in a later revision, if the feature is relevant and doable.
Using this attribute could lead to a design time technique, where F# user would flip the attribute on while adding cases and removing it once all code changes and tests are done or to keep only in debug build; bringing some extra confidence of having reviewed all the matches.
Pros and Cons
The advantages of making this adjustment to F# are:
The disadvantages of making this adjustment to F# are:
_
is used (many places...)Extra information
Estimated cost (XS, S, M, L, XL, XXL): medium
rfc: small
compiler: medium
tests: medium-large
documentation / guidelines: medium
Related suggestions: #414
Affidavit
The text was updated successfully, but these errors were encountered: