2012-07-18 86 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