2012-07-18 66 views
2

我敢肯定,我碰到了某种限制,但我不明白:可能的F#类型推断限制

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 

有人可以带来一些轻了呢?我没有找到任何相关的问题,对不起,如果我复制了一些东西。

回答

5

的问题是,如果doSomething2有类型((string->'a) -> 'b) -> unit,然后'a'bdoSomething2每次调用,这是不是你想要的过程中是固定的 - 过程中单你的情况'a需要既intfloat处理调用doSomething2

看来你真正想要的更像是:doSomething2 : (forall 'a. (string -> 'a) -> 'a) -> unit,但是这种直接通用量化在F#中不存在。正如你发现的那样,解决这个问题的方法是使用带有泛型方法的类型。

即使F#确实支持forall类型,正如我在评论推论中所提到的,仍然是不可能的。考虑您的doSomething2函数 - 我们知道runner需要能够将string -> int类型的输入转换为某种输出类型,并将string -> float类型的输入转换为某种(可能不同)输出类型。下面是doSomething2几个不同的签名,所有满足这一要求:

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

注意,没有这些类型的比别人更普遍的,它们都是不相容。在第一种情况下,我们可以将id传递给该函数,在第二种情况下,我们可以将run4传递给它,在第三种情况下,我们可以传递ignore(但这些函数都不与其他可能的签名兼容!)。

+0

你感觉像编程在一个非常高的水平,然后像这样的东西即将出现有点伤心。 我觉得应该可以采取任何“run4”,并使其成为一个参数。 – matthid 2012-07-18 19:07:50

+1

有些语言支持通用限定符,比如我对'doSomething2'的假设定义,但我相信类型推断在这些语言中是不可判定的 - 您需要一个明确的注释来定义这样的函数。 – kvb 2012-07-18 19:25:39

+0

感谢您的补充说明。 – matthid 2012-07-18 19:34:34