matthid
matthid

Reputation: 1704

Possible F# type inference limitation

I'm quite sure that I run into some kind of limitation, but I do not understand it:

type IRunner = 
    abstract member Run : (string -> 'a) -> 'a
type T() = 
    let run4 doFun = doFun "4"
    let run5 doFun = doFun "5"
    let parseInt s = System.Int32.Parse(s)
    let parseFloat s = System.Double.Parse(s)
    let doSomething () = 
        let i = parseInt |> run4
        let f = parseFloat |> run4
        f |> ignore

    // Make it more generic ->
    //let doSomething2 (runner:(string->'a)->'b) =
    let doSomething2 runner = 
        // Error on the following lines with both declarations
        let i = parseInt |> runner
        let f = parseFloat |> runner
        f |> ignore

    // Want to do something like
    let test () = 
        doSomething2 run4
        doSomething2 run5

    // Workaround
    let workaround (runner:IRunner) = 
        let run f = runner.Run f
        let i = parseInt |> run
        let f = parseFloat |> run
        f |> ignore

Can somebody bring some light over this? I did not find any related question, sorry if i duplicated something.

Upvotes: 2

Views: 388

Answers (1)

kvb
kvb

Reputation: 55184

The problem is, if doSomething2 has type ((string->'a) -> 'b) -> unit, then 'a and 'b are fixed during each invocation of doSomething2, which isn't what you want - in your case 'a needs to treated as both int and float during a single invocation of doSomething2.

It seems like what you really want is more like: doSomething2 : (forall 'a. (string -> 'a) -> 'a) -> unit, but that kind of direct universal quantification doesn't exist in F#. As you've discovered, the way to work around this is to use a type with a generic method.

And even if F# did support forall types, as I mentioned in a comment inference still wouldn't be possible. Consider your doSomething2 function - we know that runner needs to be able to take an input of type string -> int to some output type and an input of type string -> float to some (possibly different) output type. Here are several different signatures for doSomething2 that all meet this requirement:

  1. forall 'a. 'a -> 'a
  2. forall 'a. (string -> 'a) -> 'a
  3. forall 'a. 'a -> unit

Note that none of these types is more general than the others, they are all incompatible. In the first case, we could pass id to the function, in the second case, we could pass run4 to it, and in the third case, we could pass ignore to it (but none of those functions is compatible with the other possible signatures!).

Upvotes: 5

Related Questions