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

Constructor syntax for discriminated unions type defs #1376

Closed
5 of 6 tasks
3xau1o opened this issue Jul 28, 2024 · 8 comments
Closed
5 of 6 tasks

Constructor syntax for discriminated unions type defs #1376

3xau1o opened this issue Jul 28, 2024 · 8 comments

Comments

@3xau1o
Copy link

3xau1o commented Jul 28, 2024

I propose to allow constructor syntax on DUs without dropping existing one

Currently F# DUs use Ocaml syntax exclusively

type Shape =
    | Rectangle of width : float * length : float
    | Circle of radius : float
    | Prism of width : float * float * height : float

That is inconsistent with usages elsewhere for instance both instantiation and pattern matching use constructor call syntax

let rect = Rectangle(length = 1.3, width = 10.0)
let circ = Circle (1.0)
let prism = Prism(5., 2.0, height = 3.0)

let getShapeWidth shape =
    match shape with
    | Rectangle(width = w) -> w
    | Circle(radius = r) -> 2. * r
    | Prism(width = w) -> w

Other ML languages already allow constructor syntax for union type definitions, for instance rescript, previously known as ReasonML

// Rescript lang snippet
type account =
  | None
  | Instagram(string)
  | Facebook(string, int)

Pros and Cons

The constructor syntax on DUs type definition is cleaner, consistent with F# instantiation and pattern matching

Suggestion is to allow constructor syntax as alternative syntax without dropping existing one

Affidavit (please submit!)

Please tick these items 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
  • This is a language change and not purely a tooling change (e.g. compiler bug, editor support, warning/error messages, new warning, non-breaking optimisation) belonging to the compiler and tooling repository
  • 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
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate

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.

@3xau1o 3xau1o changed the title Constructor syntax for discriminated unions Constructor call syntax for discriminated unions type defs Jul 28, 2024
@3xau1o 3xau1o changed the title Constructor call syntax for discriminated unions type defs Constructor syntax for discriminated unions type defs Jul 28, 2024
@kerams
Copy link

kerams commented Jul 28, 2024

I don't think this would fly even as a proposal for the original syntax for DU definitions. What you're suggesting is inconsistent with tuple constructors and method invocation syntax - the constituent elements of (x, y, z) are values, but with | Facebook (string, int) you're using a comma-separated list of types (or field name, type pairs) . Asterisks are the separator in tuple types, so I think at best one could argue for the removal of of, but even that would have issues (like when you'd want to use an actual tuple for a DU case field).

@3xau1o
Copy link
Author

3xau1o commented Jul 28, 2024

@kerams that was a snippet from Rescript lang, however it's not hard to imagine it for F#, even with tuples would be like a regular function signature

let getDistance ((x1,y1): float*float) ((x2,y2): float*float) =
    (x1*x2 - y1*y2) 

just replace the let with union bar | and remove the function body

@brianrourkeboll
Copy link

brianrourkeboll commented Jul 28, 2024

Is this not #1129, more or less?

@3xau1o
Copy link
Author

3xau1o commented Jul 28, 2024

@brianrourkeboll that one is specific to tuple syntax, this one would be to wrap in parenthesis instead of prefixing with of

@brianrourkeboll
Copy link

@brianrourkeboll that one is specific to tuple syntax, this one would be to wrap in parenthesis instead of prefixing with of

All right, thanks for the clarification. However:

  1. This proposal does share with Allow comma and parentheses for tuple type signatures #1129 the use of parentheses and , instead of * to specify a tuple-like type signature.
  2. I doubt this proposal would be implemented unless Allow comma and parentheses for tuple type signatures #1129 were also implemented.

If #1129 were implemented, syntax like

type U =
    | A of (int, string)

would presumably already be allowed.

So I guess this proposal could be construed as an add-on to #1129 in the sense that both are trying to bring the syntax of types (and type signatures) closer to the syntax of values (expressions/patterns).

@3xau1o
Copy link
Author

3xau1o commented Jul 29, 2024

The parenthesis/comma tuple proposal would fix the main pain point, as mentioned by @brianrourkeboll the proposed syntax on #1129 seems fairly decent

@3xau1o 3xau1o closed this as completed Jul 29, 2024
@Tarmil
Copy link

Tarmil commented Jul 29, 2024

If #1129 were implemented, syntax like

type U =
    | A of (int, string)

would presumably already be allowed.

It would, but it wouldn't mean the same thing. Just like currently, the following two are different:

type U = | A of int * string
type U' = | A of (int * string)

The latter allocates a tuple, whereas the former stores the two values directly in the A.

That being said, I can't imagine comma syntax being implemented only for some uses of the asterisk type operator. That would be a crazy inconsistency. So if (int, string) ever makes it into the language, then surely so will some form of comma syntax for unions, whether it be A of int, string or A(int, string) as proposed here.

@brianrourkeboll
Copy link

@Tarmil Yeah, I understand that distinction in the current syntax, but I don't think it could be the same way in #1129, since the following are currently legal:

match 1, 2 with
| x : int, y -> x + y // y is a value of type int.
let f (x : int, y) = x + y // y is a value of type int.

I.e., at least one set of parentheses would always be required for backwards-compatibility, since in many scenarios symbols after , are parsed as values, not types.

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

4 participants