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

Compiler should infer union case names from known type information #1074

Open
4 of 5 tasks
stevenbrix opened this issue Sep 10, 2021 · 8 comments
Open
4 of 5 tasks

Compiler should infer union case names from known type information #1074

stevenbrix opened this issue Sep 10, 2021 · 8 comments

Comments

@stevenbrix
Copy link

stevenbrix commented Sep 10, 2021

I propose we enable the compiler to infer the proper union case name in a match statement from known type information.

The existing way of approaching this problem in F# is that the compiler uses the last type that is declared. It's confusing b/c it can also matter which order your open statements are in.

Below is an example of code that should compile:

type A =
| Updating
| Done

type B =
| Updating
| Complete

type C = {
    Status:A
}

[<EntryPoint>]
let main argv =

    let c = { Status = Done }

    match c.Status with
    | Updating -> 0
    | Done -> 1

Pros and Cons

The advantages of making this adjustment to F# are:

  1. It will be easier to refactor code
  2. It will be easier to maintain code

The disadvantages of making this adjustment to F# are ...

I'm not sure

Extra information

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

Related suggestions: (put links to related suggestions here)

I filed this issue: dotnet/fsharp#12131

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

** @dsyme said that this behavior is by design, but I don't think it would be breaking. code that did compile wouldn't break due to this change.

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.

@cartermp
Copy link
Member

This is related to several other quirks, often when mixing unions and modules with the same name. Definitely in favor of this.

@dsyme
Copy link
Collaborator

dsyme commented Sep 14, 2021

I suspect there are cases where this could be a breaking change, at least we'd need to be very careful

An example is where active patterns are in scope, but the union cases are not:

module M =
    type U = A of int | B

let (|A|_|) (x: M.U) = Some 3

let f (x: M.U) =
    match x with
    | A v -> v
    | _ -> 4

f (M.A 5)

Today this returns 3 because A in the pattern refers to the active pattern (|A|_|). If we use type-directed label resolution in preference to the active pattern A then this will now return 5.

@Happypig375
Copy link
Contributor

Very much in favor of this. Currently using Fable.React is annoying because of duplicated case names across HTMLAttr, CSSProp, and SVGAttr.

@nimzi
Copy link

nimzi commented Oct 8, 2021

This feature would be a tremendous win for domain driven design situations where union types are treated as sets. In such cases elements of the sets have the same name. Naturally one typically writes many functions that cast between the sets. Currently such code is overly verbose and not F#-like. Perhaps there is a way to add the feature while retaining the backwards compatibility.

@Happypig375
Copy link
Contributor

@nimzi A more tremendous win would be #538. I have always thought that nominal DUs are less suitable for describing "or" types in domain-driven design where structural unions are the norm.

@nimzi
Copy link

nimzi commented Oct 21, 2021

@Happypig375 Very much agree with you, but I believe both mechanisms are important. In certain cases I prefer DUs to curb the chaos. The two RFCs would be a TREMENDOUS step forward. Wondering if you have ideas on how we could increase their visibility?

@Happypig375
Copy link
Contributor

For #538, @dsyme has stated before that the rough order of proposals to be implemented by him starts with analysers, then #538. I don't think we really need visibility, rather someone to implement them in actual.

@jwosty
Copy link
Contributor

jwosty commented Jul 8, 2023

I suspect there are cases where this could be a breaking change, at least we'd need to be very careful

Today this returns 3 because A in the pattern refers to the active pattern (|A|_|). If we use type-directed label resolution in preference to the active pattern A then this will now return 5.

Perhaps this could be solved by making type-directed resolution the tiebreaker, when name-directed resolution is found to be ambiguous.

Here's another potential corner case showing that it would be more nuanced than just giving active patterns "higher precedence:"

type IFoo = interface end

let (|A|_|) (x: IFoo) = Some 3

type U =
    | A of int | B
    interface IFoo

let f (x: U) =
    match x with
    | A v -> v
    | _ -> 4

f (A 5)

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

No branches or pull requests

6 participants