When a type declaration is made in the function header, either the groupBy or sumBy function incorrectly inferences another non related type.
This is more of a nuisance than anything else, but in my admittedly uneducated view the compiler should have enough information to correctly infer the type
Repro steps
The code below should reproduce the error
Expected behavior
Compiler correctly interprets the type
Actual behavior
The type OrderHistoryItem does not match the type CartItem
The error occurs for itms in the sumBy function - highlighted under _itms_
type CartItem = {
ItemID: string
ItemDescription: string
ItemPrice: double
Qty: int
}
type OrderHistoryItem = {
SlipNumber: string
OrderDate: DateTime
MealTimeID: string
PickupLocation: string
ItemID: string
ItemDescription: string
ItemPrice: decimal
Qty: int
}
let subtotalItems (items:CartItem list) =
items
|> List.groupBy (fun x -> (x.ItemID, x.ItemDescription, x.ItemPrice) )
|> List.map ( fun ((id,desc,price),itms) -> {
ItemID= id
ItemDescription= desc
ItemPrice= price
Qty= List.sumBy (fun x -> x.Qty) itms
} )
Known workarounds
Reorder the type declarations of CartItem and OrderHistoryItem
Add redundant type declaration to sumBy function as below:
let subtotalItems (items:CartItem list) =
items
|> List.groupBy (fun x -> (x.ItemID, x.ItemDescription, x.ItemPrice) )
|> List.map ( fun ((id,desc,price),itms) -> {
ItemID= id
ItemDescription= desc
ItemPrice= price
Qty= List.sumBy (fun (x:CartItem) -> x.Qty) itms
} )
System Info
VsCode 1.40.1
dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 2.1.802
Commit: 177d0b2525
Runtime Environment:
OS Name: ubuntu
OS Version: 16.04
OS Platform: Linux
RID: ubuntu.16.04-x64
Base Path: /usr/share/dotnet/sdk/2.1.802/
Host (useful for support):
Version: 2.1.13
Commit: 1a165a1588
.NET Core SDKs installed:
2.1.4 [/usr/share/dotnet/sdk]
2.1.802 [/usr/share/dotnet/sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.All 2.1.13 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.13 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.0.5 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.13 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
@tangledupinblue as a workaround, if you use itms |> List.sumBy (fun x -> x.Qty) the initial sample works without extra annotation.
@smoothdeveloper - interesting, tried it in my project and it does work. Thanks
@tangledupinblue the key rule to remember about type inference is: if the variable is evaluated (that is: seen by the compiler) before the call to the function, the compiler knows its type.
I.e., it would have also worked if you had something like let x = itms prior to the List.sumBy function. That is, typically _any_ prior usage will usually have the compiler infer the type correctly. That's why so much F# code is written with |> piping.
The reason behind this is simple: to prevent multiple passes over the source to do type inference, the compiler cannot look-ahead too much. Hence, the moment it "sees" List.sumBy, it does not yet know which generic version of that function to choose, and therefor, what the types of its arguments are.
If you hadn't had two times the same name for the record field Qty the problem wouldn't have arisen either. For the same reasons of "single pass", the compiler remembers the last record that has that field.
This is currently by design as per what @abelbraaksma explained, so I'll close this on that basis. Closing this in favor of a comment here: https://github.com/fsharp/fslang-suggestions/issues/594#issuecomment-554424789
As it stands, this is not a bug even if it kind of feels that way.
Most helpful comment
This is currently by design as per what @abelbraaksma explained, so I'll close this on that basis. Closing this in favor of a comment here: https://github.com/fsharp/fslang-suggestions/issues/594#issuecomment-554424789
As it stands, this is not a bug even if it kind of feels that way.