The design suggestion Support first-class lensing / lenses This RFC covers the detailed proposal for this suggestion.
- Approved in principle
- Suggestion
- Details: under discussion
- Implementation: WIP
Enable updating nested record field with "with" syntax.
This improves readability in cases where nested record field values need to be updated.
For example, take the following code:
type Street = { N: string }
type Address = { S: Street }
type Person = { A: Address; Age: int }
let person = { A = { S = { N = "Street 1" } }; Age = 30 }
let anotherPerson = { person with A = { person.A with S = { person.A.S with N = person.A.S.N + ", k.2" } } }
let anotherPerson1 = { person with A.S.N = person.A.S.N + ", k.2" }
Transform the AST in TypeChecker.fs to expand the new syntax into the current form.
let anotherPerson = { person with A.S.N = person.A.S.N + ", k.2" }
Becomes:
let anotherPerson = { person with A = { person.A with S = { person.A.S with N = person.A.S.N + ", k.2" } } }
Need to consider:
- Handle the possible ambiguity of specifying type name vs field name
- Group nested field from the same parent
- All nested field parts need to be declared on record type
- Collaborate with anonymous records feature
- Check IntelliSense
- Investigate other language features that should support nested paths like named arguments
- Check same field is not updated twice within statement
This change allows updating nested fields, using dot notation, within one copy and update statement.
let anotherPerson = { person with A.S.N = "1" }
Multiple fields with differing levels of nesting can be updated within the same expersssion.
let anotherPerson = { person with A.S.N = "1"; Age = 1; }
Fields can be accessed through Namespace, Module or Type name.
The implementation expands the nested syntax into the existing AST for nested updates so qualified access is checked through the same mechanism.
TypeName Access
let anotherPerson = { Person.A.S.N = "1"; Person.Age = 1; }
ModuleOrNamespaceName Access
let anotherPerson = { ModuleOrNamespaceName.A.S.N = "1"}
ModuleOrNamespaceName.TypeName Access
let anotherPerson = { ModuleOrNamespaceName.Person.A.S.N = "1" }
Additional complexity in the compiler.
This is not a breaking change, and is backwards-compatible with existing code.
Q: What about indexers, e.g.
let anotherPerson1 = { person with A.[3].N = person.A.[3].N + ", k.2" }
A: Need to look at it
Q: What happens to cases where A.S is mentioned twice?
{ person with A.S.N = person.A.S.N + ", k.2"; A.S.M = person.A.S.M + ", k.3" }
A: Already implemented will compile to
{ person with A = { person.A with S = { person.A.S with N = person.A.S.N + ", k.2"; M = person.A.S.M + ", k.3" }
Q: What about similar features in the language that name fields, especially mutating property setters
Person(A.C.N = 3, A.C.M = 4)
A: Need to look at it after we finish with the records
Q: Will it work with struct records without introducing lot's of struct copy?
A: The current implementation is expanding the simplified AST into the existing one and will have the same struct copy issue mentioned by @zpodlovics. However, as this issue is present in the original copy and update expression this would have to be fixed as well as updating the new implementation. This will be investigated further once the open tasks are completed.
- Do not implement this feature