Skip to content
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

Predefined value cases in discriminated unions #1051

Closed
5 tasks done
CaptnCodr opened this issue Jul 25, 2021 · 8 comments
Closed
5 tasks done

Predefined value cases in discriminated unions #1051

CaptnCodr opened this issue Jul 25, 2021 · 8 comments

Comments

@CaptnCodr
Copy link

CaptnCodr commented Jul 25, 2021

I propose we can add predefined cases in DUs.

I have noticed following problem in e.g. a console application:

let matchCommand (command: string) =    
    match command with
    | "a" -> doSomething()
    | "b" | "c" | "d" -> doWhatever()

You get a warning (FS0025) that indicates that any other cases are not covered by the pattern:
FS0025: Incomplete pattern matches on this expression. For example, the value 'aa' may indicate a case not covered by the pattern(s).

You have to create a case above e.g.:
| _ -> printfn "Command not found"

Now to my proposal:
The cases in the example above could be outsourced like:

type Alphabet =
    | BCD = "b" | "c" | "d"

Then you can use the case like a normal DU case:

let matchCommand (command: string) =
    match command with
    | "a" -> doSomething()
    | BDC -> doWhatever()
    | _ -> printfn "Command not found"

I thought of something other to cover all cases in a pattern:

type Match =
    | Key = "A" | "B"
    | Another = "X" | "Y" | "Z"
    | Numbers = 1 | 2 | 100 | 123
    | Greater x = x when x > 100

let matchIt x =
    match x with
    | Key -> "Got the key!"
    | Another -> "Bites the dust."
    | Numbers n -> $"Number: {n}"
    | Greater x -> $"{x} is greater than 100"

Here we have covered all cases in this pattern and we are clean as in a discriminated unions.
As in a DU case, you can use the matched value in the code block that follows (3rd & 4th case) like in a usual match-expression.
Perhaps, all pattern matching case options should be possible as shown in the F# reference.

The existing way of approaching this problem in F# is to just write down all cases in pattern matches. Perhaps more often than we actually want.

Pros and Cons

Advantage of these changes are for example:

  • full covered patterns / type safety
  • easy to understand (good for beginners and advanced)
  • reusable and combinable cases (| Key | Another -> someCode())
  • simple syntax

Disadvantages does not come in mind. May have to be discussed.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S-M

Affidavit (please submit!)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I or my company would be willing to help implement and/or test this

For Readers

If you would like to see this issue implemented, please click the 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.

In the end I notice, that this proposal feels like a mix of DUs, enum types and a bit of active patterns. Maybe more steroids in enums. 😄

@CaptnCodr CaptnCodr changed the title Predefined value cases in discriminated unions (enum like) Predefined value cases in discriminated unions Jul 25, 2021
@BentTranberg
Copy link

I don't understand why FS0025 should be considered a problem rather than the language feature it actually is. Nor do I see what that has to do with the feature proposal. I can't see how this will solve problems or ease coding to the extent that the feature is justified, compared to just exploiting other features of the language. I don't see that the Pros are there, compared to what we already have.

@CaptnCodr
Copy link
Author

CaptnCodr commented Jul 26, 2021

This come to my mind while I wrote this proposal that this warning comes up. This is one side of the medal.
When you write the cases in an application more than once then you may have to outsource them in a variable or in a union case. That's the main part of this proposal.

@RomanK-TG
Copy link

Rather than introducing new feature into the language you'd better use ActivePatterns that solves exactly you problem.

@CaptnCodr
Copy link
Author

CaptnCodr commented Aug 2, 2021

I find this totally uncluttered to have an active pattern for each choice (~20 pcs) e.g.:

let (|Key|_|) x = if x = "key" || x = "-k" then Some x else None
or:
let (|Key|_|) x = match x with | "key" | "-k" -> Some x | _ -> None

and:

match arg with 
| Key _ -> doSomething()
| ...

Furthermore, I have a discarded parameter (_) when I use the active pattern and don't want to use the matched argument.

Yes, this works with active patterns, but the code looks not so nice anymore. Perhaps, it's caused that I don't like active patterns by their syntax.

Better have:

type Command =
    | Key = "key" | "-k"

@Tarmil
Copy link

Tarmil commented Aug 2, 2021

Okay but what should happen when the value doesn't match any of your type's patterns then? Presumably an exception; but that's already what happens! In this case I think what you're really asking is:

  • Haskell-style pattern synonyms
  • a way to locally ignore FS0025 (so that the exception is thrown at runtime but the compiler doesn't complain)

which is 2 quite separate suggestions.

@voronoipotato
Copy link

Another solution would be to allow active patterns to have more than 7 cases. As a bonus it would be extremely cool if it had a backing type that matched the name of the active pattern "names" for fully described aka complete active patterns. That way you could store the result instead of computing it every match.

@dsyme
Copy link
Collaborator

dsyme commented Jun 14, 2022

This addition isn't the kind of thing we're going to add to F# union type definitions.

There may be some kind of alternative suggestion lurking here for adhoc unions including singleton values like strings, similar to typescript, however that would be better tracked in a separate suggestion

@dsyme dsyme closed this as completed Jun 14, 2022
@voronoipotato
Copy link

@dsyme Thanks for going through these!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants