-
Notifications
You must be signed in to change notification settings - Fork 22
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
Match anonymous record's type inference with nominal records inference #759
Comments
I think my comment applies here, but if I need to make a separate issue let me know. We ran into this today. We were forced to twist our code into a particular shape in order to make anonymous records worth using. type MyThing =
{ MyName: string
MyStatus: MyStatus }
async {
match! Db.read<{| Name: string; Status: string |}> config query with
| Error ex -> ...
| Ok list ->
// does not compile, cannot determine type of item
let toStatus item =
{ MyName = item.Name
MyStatus = MyStatus.fromString item.Status }
return Ok (List.map toStatus list)
} Here's how I had to make it work: async {
match! Db.read<{| Name: string; Status: string |}> config query with
| Error ex -> ...
| Ok list ->
let returnList =
list
|> List.map (fun item ->
{ MyName = item.Name
MyStatus = MyStatus.fromString item.Status }
)
return Ok returnList
} This is not the actual code, but an example for demonstration. This 2nd example is enough for type inference to determine the type of This would be solved if anonymous record type definitions were exposed to the type inference engine inside the scope where they are declared. So that the type inference would realize without annotation that the |
@cartermp Any update on this? |
This is by-design based on the RFC, and I don't immediately expect we will change it. |
This is correct - the anonymity of anonymous records doesn't come without cost, nor do they give benefits in all situations. In general, type annotations are required to get decent name resolution and comprehensible errors for |
Our team has attempted to use anonymous records in place of transient records and tuples multiple times, but with no discernible benefit so far. It seems our code base just doesn't have or we lack instincts to recognize the beneficial use cases in our code. My comments and ergonomic suggestions above seem to expose that fact. |
The List.someFn (fun x -> ...) myType vs. myType |> List.someFn (fun x -> ...) Isn't limited to anonymous records by any means here, though. This is a general F# tension point. The general guideline here is to use pipes to signal to the typechecker what something is. |
@cartermp Yes, that is a common issue with anonymous functions. But in this case, the first code listing would have worked by only replacing the anonymous record with a regular record. Because type inference would be aware of a regular record definition for the |
You can also define an anonymous record type abbreviation and use a type annotation. It's essentially the same as defining a record type though
Yes, this is a feature people will try to use in various ways and it won't always give the results the user hopes for. I wrote this up somewhere, though it's not specifically in the RFC, but basically as soon as you need type annotations on code consuming the data then you may as well have defined a record type. This is a major reason why the RFC talks so much about "a smooth path to nominalization", so you don't get trapped in using anon records in sophisticated ways, and then pay an enormous cost to back out. That is, you may have experimented with them and decided not to use them, but at least I assume you didn't get stuck in a corner where it was effectively impossible or very costly to back out of that experiment. |
So it seems rather the purpose is to start out anonymous, such as in a REPL or data exploration. Then later solidify into named records as needed. That makes sense. And I suppose we assumed differently of them because we don't have those uses at present. |
Well, actually that's exactly my scenario. I'm trying to use them in ad-hoc queries in a REPL and they don't work, replacing them by nominal records solves the issue, but it doesn't makes any sense. It's really sad, because I was always pushing to get anon records in F#, in order to get these kind of queries working, which defeats the purpose of using F# as opposed to C# in Linqpad. If there's a solid decision of not matching the type inference mechanism of nominal records, is there a way to improve this type inference? Maybe using multipass inference, as it's being discussed in #594 ? |
Could you clarify - do you mean they don't work (unexpected), or that they require more type annotations than is palatable? I assume the latter.
AFAIK inference for linqpad-like scenarios should be at least as good as for C# intra-method inference for anonymous C# objects, and likely better. Could you give examples if not? thanks |
Yes, I meant they require type annotations. |
Closing per my comment here: #759 (comment) This is all really by design |
I propose we use the same type of inference as for nominal records
The way it works now is that anonymous records inference occurs at a later stage.
Pros and Cons
The advantages of making this adjustment to F#: Currently because anonymous records are inferred at a later stage they are more prone to be type annotated than nominal records, which defeats the whole purpose of having anonymous records.
The disadvantages of making this adjustment to F#: Nominal records inference is considered a bit weird because when two records contain the same field names, the compiler takes the last one that was declared.
Extra information
Estimated cost: M
Related suggestions: dotnet/fsharp#6699
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
The text was updated successfully, but these errors were encountered: