Reputation: 13912
I don't understand the F# type inference system for nested functions. It seems particularly broken when I use types outside simple types such as int, string, ...
here is a small example of some code printing some reflection info
let inferenceTest () =
let t = int.GetType()
let methods = t.GetMethods() |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
This works fine! No need for casting etc. Now assume that the printing is much more involved, hence we want to separate it into a nested function
let inferenceTestFailsToCompile () =
let printType t =
let methods = t.GetMethods() |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
let t = int.GetType()
printType t
This fails with "lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed..."
Why is there suddenly less information for the type system? Perhaps I could understand the problem had my printType()
function been in the same scope as my inferenceTestFailsToCompile()
When I instead create a lambda that takes t
as its closure, the typing issue goes away
let inferenceTestLambda () =
let t = int.GetType()
let printType =
let methods = t.GetMethods() |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
printType
Upvotes: 4
Views: 187
Reputation: 24996
Basically type inference is done top to bottom left to right. There are lots of exceptions but I won't go into them.
In the first example the inferencing engine had enough information to infer the types correctly.
let inferenceTest () =
let t = int.GetType()
let methods = t.GetMethods() |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
became
let inferenceTest () =
let (t : type) = int.GetType()
let methods = t.GetMethods() |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
became
let inferenceTest () =
let (t : type) = int.GetType()
let methods = (t.GetMethods() : System.Reflection.MethodInfo []) |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
became
let inferenceTest () =
let (t : type) = int.GetType()
let (methods : seq<string>) = (t.GetMethods() : System.Reflection.MethodInfo []) |> Seq.map(fun m -> m.Name)
printfn "%s" <| String.concat ", " methods
which was helped by the use of |>
and fails as
let methods = Seq.map (fun m -> m.Name) (t.GetMethods())
In the second example this line is inferred as .
let printType (t : 'a) () =
which causes the error on
let methods = t.GetMethods() |> Seq.map(fun m -> m.Name)
because the type for t
is generic and does not give enough info for use with t.GetMethods()
.
To solve these problems, I use Visual Studio and move the mouse over the variables to look at the type. Then if I find a type that is not right I start adding the type definitions. This usually leads to fixing the error or uncovers a bug in my code.
EDIT:
This is part of an answer from Why is F#'s type inference so fickle? by Robert Harvey
F# uses one pass compilation such that you can only reference types or functions which have been defined either earlier in the file you're currently in or appear in a file which is specified earlier in the compilation order.
I recently asked Don Syme about making multiple source passes to improve the type inference process. His reply was
"Yes, it’s possible to do multi-pass type inference. There are also single-pass variations that generate a finite set of constraints.
However these approaches tend to give bad error messages and poor intellisense results in a visual editor."
Upvotes: 5