Below are two examples of how you can determine if an expression returns true for a given list.
Example 1 uses List.exists as per the Microsoft documentation and passes the expression and list as the second parameter
Example 2 uses a pipe to pass List.exist an explicit type of list as per the fsharp.org documentation
type Person = { name: string }
type Fox = { name: string }
let itemExists =
let people : Person list = [{ name = "Robert" }]
//This won't compile: Type mismatch
let example1 : bool = List.exists (fun i -> i.name = "X") people
//This will compile
let example2 : bool = people |> List.exists (fun i -> i.name = "X")
0
The Problem
The problem here is that for example1
the order of the record types matter and in its above state will not compile with a type mismatch
.
The lambda is implicitly determining the type of i
based on name
. But name is shared by both type Person
and type Fox
. It therefore takes the first matching type in the file or namespace from the BOTTOM. In this case i is of type Fox
and is incompatible with the passed person
list.
Where this becomes dangerous is when your types are spread out through the application or even in a separate library. If a new record type is added with the same property name and type it will result in your consuming application breaking if it were to take the update.
Top comments (1)
I think this is a—not exactly a bug, but an inadequacy of the type inference system. Other things that would fix your problem are,
It seems that F# type inference works from left-to-right. I appreciate that type inference is more ambiguous in an object-oriented language for precisely the reason you laid out, that classes and records are namespaces so properties and methods don't uniquely identify types. But the big mistake of F# is, when you're dealing in combinators like map and filter and exists, the type inference works if you put the collection first (since it's unambiguously typed in almost all cases). So why did they put the functional argument first?