-
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
Infer record names from types #1034
Comments
Of the two different suggestions here, I find type ItemQuantity of float more interesting. It's fairly simple and straight-forward and in principle it's not so bad to have a short cut for single case DUs. However we really need to look a this issue holistically and doing so is quite a tricky issue for F#, where any move outside the existing status quo just risks "yet another way" of doing things which may be worse than the problem being solved. The huge downside is that it really gives this alternative syntax for record-like declarations without people realising that it's actually a discriminated union. The user sees: type ItemQuantity of SomeValue: float * SomeValue2: float and they say "boy, that looks like a record to me". Because, of course the distinction between records and single-case DUs is somewhat artificial, though in practice they have very different properties across the language design. So we have to be really, really careful here. An alternative more radical direction is to have alternative, new syntax for record-like things and revise everything else accordingly. For example either type ItemQuantity of SomeValue: float * SomeValue2: float or more radical shift: type ItemQuantity(SomeValue: float, SomeValue2: float) Anything in this direction has major ramifications with many questions to be answered
As an aside, the syntax // Possible syntax revision for signatures
module M =
val someFunction(x:int, y:int): int
type C(x: int, y: int) =
member P: int
member Method(v: int, u:int) : int I'm actually not opposed to a revision of the signature syntax along these lines. But I'm pointing out there are potential ramifications right across the language design. F# would certainly still be F# with these revisions, but it trends towards a considerable revision that needs to be thought through. |
Another question is what would be implications of these parameters looking like parameters in a object type implicit constructor? If they're going to be accessible like record fields, then are we going to change it for existing object types constructor parameters? This would be quite a big breaking change. And if these two same-looking syntaxes are considered different, it might be difficult to distinguish them, especially for newcomers. Another question is what naming conventions should these parameters use? Record fields are |
I think its in line with F#'s type system to use type inference by default and declare a specific one, when suitable. I always found it conflicting with this strategy, to specify types manually in record types. Particularly when I want my code so type safe as possible. I see so many examples online, where all the record types and discriminated unions are strings and integers. Do we have 73 strings and 283 integers in our code? Or are these actually nearly all unique types. This proposal suggests the usage of inferred types, who are unique and by that, more type safe and more expressive in type declarations. |
Yes, exactly.
Right, all these and other questions would need to be answered. |
People are using single case DUs because of a syntactic preference rather than specifically intending for discriminated unions. The language itself should use language structures (in the literal sense) properly and if there is any syntactic sugar for haskell newtypes, it should avoid creating DUs with only one case and with conflated case name and type name, and instead create records. https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1073-record-constructors.md would create the syntax: [<Struct; RequireQualifiedAccess>] // these annotations not strictly necessary
type ItemQuantity = { Value:float }
let x = ItemQuantity(1.) This is very similar to and captures the benefits of [<Struct>] // needs this for equality semantics for consistency with class syntax
type ItemQuantity(Value: float)
let x = ItemQuantity(1.) That said, a class syntax which exposes primary constructor inputs as properties would be clean: [<ExposeConstructorInputs>]
type V2(X:float, Y:float) =
member EuclideanNorm = sqrt(X*Y + Y*Y)
let x = V2(2.,3.).X |
Possibly:
I'd love to see a prototype of this |
Also type V2(val mutable X:float, val mutable Y:float) =
member EuclideanNorm = sqrt(X*Y + Y*Y)
let x = V2(2.,3.).X and type V2(internal val X:float, internal val Y:float) =
member EuclideanNorm = sqrt(X*Y + Y*Y)
let x = V2(2.,3.).X There would also be a question of whether attributes on that target the things existence as a parameter, or backing field, or property |
Hmmm... Those still need the field names, which this proposal aimed to have them inferred. |
Yes, any cased initial letter can be converted automatically, having PascalCase for records and camelCase for parameters. Errors can be raised on collision. |
Infer record names from types
F# is a great language for domain-driven design with single-case unions, DUs for "or" types, and records for "and" types. Some may even say that F# files containing domain types only can be read by non-programmers easily!
(Extracted from https://github.com/matthewcrews/ddd-with-fsharp/blob/master/Examples/Domain4.fsx)
But there are lots of duplication that hinders this objective! Wouldn't it be nice if we can write:
In essence, we reduced
to
. The deduplication for single-case unions belong to #727, the deduplication for "or" types belong to #538, and here I'll propose the deduplication of "and" types.
A record or anonymous record written as
will be interpreted as
where the constituent names are interpreted as types, highlighted as types, and the names will be inferred from the types.
When one of the types is a generic specialization, the name in source will be used:
However, having generic type parameters inside a type without a corresponding field label is not allowed.
The alternative is to infer a name of
List<'T>
which is inconsistent with the type when specialized.When dot-access notation is used, only the last part is used as the name.
When two names collide, error.
The duplicate name error also applies to:
Pros and Cons
The advantages of making this adjustment to F# are
The disadvantage of making this adjustment to F# is that this introduces additional rules and syntax to learn. However, #653 is approved and has the same disadvantageous properties but for record construction.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): M
Related suggestions:
#727 and #538 - Deduplication for single-case unions and "or" types
#653 - Infer record labels from expressions
This proposal has an analogy for record creation as shown in that proposal. However, it uses name inference via
nameof
, butnameof(seq<int>)
producesseq
which would not be desirable here. Even withnameof(int seq)
being an error currently, the only consistent output for it would be alsoseq
as shown in #953. Moreover, it cannot purely rely onnameof
name lookup either, because it wouldn't make sense to have local bindings starting with uppercase letters just to infer names, thereforewould result in a value of type
{| A : int; B : int |}
, which makes it more complex than just anameof
lookup, like seen in this proposal.#600 - Intersection types
Intersection types provide an alternate way to solve this problem. However, they not only take a huge implementation effort, but also:
#747 - Infer record field types from names
That proposal which bears syntactical resemblance to this proposal was shot down quickly after being posted. This proposal is different from it because:
type
keyword, and conflates name deduplication with primitive type avoidance.Therefore, that proposal cannot be used to justify rejecting this proposal.
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 👍 emoji on this issue. These counts are used to generally order the suggestions by engagement.
The text was updated successfully, but these errors were encountered: