imply record labels from expressions
I propose we allow both records and anonymous records to imply record labels from a limited range of expressions, e.g. for anonymous records:
{| x.Name; x.Title |}
is the same as
{| Name=x.Name; Title=x.Title |}
The same would apply for records. The label names would be implied by a expr.Name lookup only.
It would be natural to do this at the same time as anonymous records.
The existing way of approaching this problem in F# is to write the label names explicitly.
Pros and Cons
The advantages of making this adjustment to F# are succinctness
The disadvantages of making this adjustment to F# are you have to know the rule, and it makes the selection of type a little more implicit, especially in the case of records.
Extra information
Estimated cost (XS, S, M, L, XL, XXL): M
Related suggestions: #207
Would it be the same way as it is in C#?
When implementing this, please make Refactor-Rename smart enough to add the label.
Example:
type R = { X : int }
let p = Point(1,2) // with properties X / Y
let r : R = { p.X }
if I now rename R.X to R.Z, then it should change the last line to let r : R = { Z = p.X }. Vice versa if you rename p.X.
Personally, I'm not a fan of this. A rule like this doesn't seem very novice-friendly and is not immediately intuitive to me. Just my two cents though.
As a counterpoint, javascript/typescript these days makes it very common to (de)construct values in this way. It's common to build objects like
let thing = { foo, bar, baz }
and get an object with "foo", "bar", "baz" keys whose values are the values bound to those names at that point.
Consequently, deconstructing is very nice in this simple case, while still allowing for the current more customizable syntax.
In short, I <3 this suggestion.
I'm not sure how I feel about this feature. I personally prefer to be explicit with the fields. Also, @0x53A raises a very good point; Refactor-Rename would need to be aware of this.
I understand the intention is to be succinct, but we can agree that isn't always a good thing. My fear is that devs will be more inclined to name their properties or fields a certain way, even if it's not correct, so that they will be able to construct records in this succinct manner.
This seems kinda confusing to me without much gain. To a beginner it might look an awful lot like you're making an array given the syntactic similarities, when it's very different.
I'd say this is common enough in other languages (both C# and JS has similar features).
What should happen if you write this?
{ foo=x.Name; x.Title }
@jwosty it depends on what types map to { foo=x.Name; x.Title }. Without the | characters that would appear to be a regular record and thus the type would have to be predefined so I would expect there would be a type such as type FooAndTitle = { foo: string; Title: string; } assuming that foo and Title are indeed string typed properties or fields otherwise a compiler error would be expected based on my experience and understanding.
Now if we are talking about {| foo=x.Name; x.Title |} instead then the expected result I would definitely expect to appear as a new anonymous record value having a property foo and Title with the values assigned from x.Name and x.Tilte respectively.
This breaks the expectation that a binding should access a value rather than a name. Rename the binding, and the behaviour changes.
There is an unsafe periphery of F#/.Net in which these expectations do not hold (reflection: nameof, typeof...) but this should not be moved into the core of the language.
I think this is somewhat similar to record punning in ocaml.
@7sharp9, looking over some of the documentation on records in OCaml it does indeed look like there is direct overlap with the concept discussed here and field and label punning. It also appears that ReasonML enjoys a similar sytactic shortcut as well as perhaps PureScript, JavaScript as of ES6, and C# with anonymous types of course. It looks like some of these implementations differ based on whether they work beyond variables/fields or work on extended property get accessors like in C#.
References:
- https://dev.realworldocaml.org/records.html
- https://v1.realworldocaml.org/v1/en/html/records.html
- https://reasonml.github.io/docs/en/record#syntax-shorthand
- https://github.com/purescript/purescript/issues/921
- http://www.benmvp.com/learning-es6-enhanced-object-literals/
- https://ariya.io/2013/02/es6-and-object-literal-property-value-shorthand
- https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types
I like this idea, but case sensitivity of names should be addressed. I think it would be weird for pre-defined records if it was case sensitive. For example, this would happen:
type MyRecord =
{
Foo : string
Bar : int
}
let createMyRecord foo bar = { foo; bar }
// error FS0039: The record label 'foo' is not defined.
For anonymous records, shouldn't it also construct fields with Pascal case to be consistent with naming guidelines?
I've wanted to suggest this feature for years now. I've been using it in my own language which is syntactically similar to F#, and I always miss being able to pun when I use records in F#. I hope to see this in the language at some point.
I'd expect this to work with struct DUs, where it is currently required to add redundant labels that you'll never use in practice. Having this for records is nice, but could arguably add to confusion, but for struct DUs at least it would remove such confusion and redundancy.
I've done a little typescript for a project and found this kind of "punning" (if that's the right term) quite natural.
I am going to remark this as approved-in-principle. We should do this.
There's an open question around casing-sensitivity that @mrakgr raised here. I think that for the initial version accepting only exact matches would be sufficient. This aligns with expectations for users familiar with JS/TS, and it reduces the 'rules' that the developer has to internalize.
I think that for the initial version accepting only exact matches would be sufficient. This aligns with expectations for users familiar with JS/TS, and it reduces the 'rules' that the developer has to internalize.
Yes, exact matches only
If I'm understanding the exact match comments right, these still wouldn't work after this feature is implemented:
type MyRecord = { A: string; B: int }
let f a b = { a; b } // used for validation
let f { a } = ... // destructure what I need
let f { a; b } = { b; ...} // convert record type, often to subset of original
match msg with // specific DUs have record values in every case
| MsgCase { a; b } -> ...
I'd have to use the existing verbose syntax for these. Or use upper case value names against the naming guidelines. Addressing the record copying case { x.A } would help some, but the others are my primary use cases where the current syntax is noisy or overcrowding the other code.
Since this issue is having some action, let me plug my own language Spiral. Compared to the last time I posted here, it is finished, and you can check it out on VS Code marketplace if you want some inspiration.
The casing thing is really unfortunate and artificially reduces the usefulness of this feature, but at the same time automatically uppercasing/lowercasing things in the language feels very wrong.
In fact, there's similar weirdness in C# when it automatically gives a name to tuple items: sometimes the name will be lowercase, sometimes uppercase, depending on the expression you put there. Add this to my reasons for disliking .NET's casing conventions 🤷
Maybe it's just my usage pattern. I use x.A syntax in a narrow set of circumstances. Typically mapping between types. For logic, local names are less troublesome when I reorganize the code. For example, split off some functionality to be reusable. I don't want a dependency on the external type. Went thru and removed record dot syntax enough times that I'm careful where I introduce it.
The casing thing is really unfortunate and artificially reduces the usefulness of this feature, but at the same time automatically uppercasing/lowercasing things in the language feels very wrong.
Yes, I agree. That said, I'm assuming that in Fable and Python F# the use of lowercase record field names is much more common - and you see it in some .NET F# as well. So I don't think the feature is without utility.
Ah, so this would represent an unofficial shift in the naming guidelines. At least, I would use camel case property names to have access to this feature for the use cases I mentioned.
so this would represent an unofficial shift in the naming guidelines
We can always mine the quarry of guidelines, carve the stone of records, and etch the new guidelines into it 🙂
@cartermp I'm into it. :) We inherited most of the guidelines from C# anyway.
This also should be applicable to struct constructors. For example:
[<Struct>]
type A =
val mutable a: string
val b: int
new (a, b) = { a; b } // same as new (a, b) = { a = a; b = b }
new (a) = { a; b = 0 } // same as new (a) = { a = a; b = 0 }