Carlo V. Dango
Carlo V. Dango

Reputation: 13912

Why can't the types be inferred for nested functions

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

Answers (1)

Guy Coder
Guy Coder

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

Related Questions