-
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
Allow recursive record assignment #1315
Comments
I would say that if we did this, I wouldn't want a keyword. I'd much prefer it to be a scoping rule that "works" in top-down order: let doot = {
FirstName = "phillip"
LastName = "carter"
FullName = $"{FirstName} {LastName}"
} |
isn't it going to introduce breaking change? type Rec = { FirstName: string; LastName: string; FullName: string }
let FirstName = "gauthier"
let LastName = "segay"
let doot = {
FirstName = "phillip"
LastName = "carter"
FullName = $"{FirstName} {LastName}"
} > val FirstName: string = "gauthier"
> val LastName: string = "segay"
> val doot: Rec = { FirstName = "phillip"
> LastName = "carter"
> FullName = "gauthier segay" } I'm more keen if we introduce #653 / #24, I think it boils to almost the same as the suggestion: let FirstName = "phil"
let LastName = "carter"
let FullName = $"{FirstName} {LastName}"
let phil = { FirstName; LastName; FullName } |
Yes, it'd technically be a breaking change, but in ordering it'd be consistent with shadowing (most "recent" scope wins), so it's something I'd actually be in favor of introducing |
@cartermp, I always thought not introducing breaking changes was a super high priority. Without some mechanism, e.g. a keyword, it would introduce massive breaking changes in my source. How about just borrowing an idea I believe was recently used in another suggestion? {
firstName = firstName
lastName = lastName
fullName = _.firstName + _.lastName
}
I know that some have argued the underscore should mean only "not used" or something like that, but the way I see it it's more like "no need to spell it out". |
@BentTranberg I also considered I also wonder about the ordering semantics, if the breaking change would be given a pass: let FirstName = "gauthier"
let LastName = "segay"
let doot = {
FullName = $"{FirstName} {LastName}"
FirstName = "phillip"
LastName = "carter" } |
@BentTranberg The underscore syntax has already been implemented for F#8 as meaning |
How about this? type Person =
{
FirstName: string
LastName: string
FullName: string
}
let person1: Person =
{ as x
FirstName = "James"
LastName = "Bond"
FullName = $"{x.FirstName} {x.LastName}" // James Bond
}
let person2: Person =
{ person1 as x with
FirstName = "Lena"
FullName = $"{x.FirstName} {x.LastName}" // Lena Bond
} edit: The last one - |
@BentTranberg you are onto something, onto being one more fsharp language designer assistant :) maybe this would be more natural: let rec person1 =
{ FirstName = "James"
LastName = "Bond"
FullName = "{x.LastName}, {x.FirstName} {x.LastName}" } as x as of now: let rec person1 = {
FirstName = "James"
LastName = "Bond"
FullName = $"{person1.LastName}, {person1.FirstName} {person1.LastName}"
} it currently gives:
|
With the last syntax I suggested, it should not be allowed to reference a field before it is assigned explicitly, if it is assigned explicitly. Without having given it much thought, I think that simple rule will be enough. I also think it feels quite F#'ish that fields are assigned before they can be used. |
I think the learning overhead is a bit higher with that syntax, because Being able to copy-and-update from another record using a variable bound using That said, I also take @cartermp 's point that not having too much additional syntax for this would be nice, which is kind of why I think |
@reinux you are right about type Foo() as this =
// ... and #501 also uses the same for binding an interface. I overall feel the suggestion isn't bringing enough, with record punning and using locals with the right name, it would become terse enough to not really bother for this edge case. The feature value increases when the members are more complex expressions, but initializing the locals is very explicit and isn't much more code, no feature needed beyond punning, it doesn't even have to pollute the scope due to the flexible scoping for bindings. let person =
let FirstName, LastName = "James", "Bond"
let FullName = $"{LastName}, {FirstName} {LastName}"
{ FirstName; LastName; FullName } note: the code gen for the bindings of tuple above isn't great, it somehow initializes an array, but is a separate issue. |
I'm not that happy either with the syntax I suggested. Especially the second one - |
|
I propose we allow fields assigned to a record to be referenced down the line, perhaps indicated by a
rec
keyword a lalet rec
:The existing way of approaching this problem in F# is to bind the value upfront, and then reference that, e.g.
Pros and Cons
The advantages of making this adjustment to F# are quality of life, helps with legibility when working with large amounts of declarative code/configuration/etc.
The disadvantages of making this adjustment to F# are one more thing to learn, though its meaning can be intuited quite easily from
let rec
or with a simple code example as in here.Extra information
Estimated cost (XS, S, M, L, XL, XXL): S (or M if parsing records piecemeal is currently non-trivial)
Related suggestions: (put links to related suggestions here)
This syntax is available in the Nix language, a DSL for a functional Linux package manager and accompanying distro.
Affidavit (please submit!)
Please tick these items 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: