2016-10-05 72 views
4

我有以下代碼:爲什麼在F#模式匹配中某些表達式中的泛型類型與obj相匹配?

type Message<'a> = | Message of 'a 

let handleMessage message = 
    match box message with 
    | :? Message<_> -> printfn "Message" 
    | _ -> printfn "Not message" 

let handleMessageEx message = 
    match box message with 
    | :? Message<int> -> printfn "Message" 
    | _ -> printfn "Not message" 

handleMessage <| Message 1 
handleMessage <| Message (1 :> obj) 
handleMessageEx <| Message 1 

在F#交互式輸出如下:

Not message 
Message 
Message 

爲什麼第一條語句結果「沒有消息」?即當匹配裝箱值F#無法檢測到它是通用類型消息< _>並且除非我指定底層類型,否則它將其設置爲對象(因此在(消息1)上失敗匹配)。

+1

不能很好地解釋,所以只是一個評論,但泛型類型不是一個真正的類型,所以不能成爲模式類型測試的目標。類型測試需要一個「具體」類型,所以通配符被可能的'obj'最普通的東西代替。你可以使函數本身也是通用的'handleMessage <'a>'',但是你需要把通用參數放在調用站點以使它工作'handleMessage <|消息1'(沒有你也會得到「Not message」) – Sehnsucht

+1

問題是obj並不是F#世界中最普通的東西,因爲它不會匹配具體的類型。我想知道爲什麼在消息<'a>的情況下F#不會離開'開放,可用於任何匹配。 –

回答

4

只要你看到_作爲類型參數,就把它想象成一個你不關心的新類型參數。如果我們據此調整你的第一個定義:

let handleMessage message = 
    match box message with 
    | :? Message<'_a> -> printfn "Message" 
    | _ -> printfn "Not message" 

那麼編譯器爲我們提供了這個有用的線索:

警告FS0064:此構造導致代碼比由類型註釋所列的那樣普通。類型變量'_a被約束爲類型'obj'。

的問題是,該類型參數必須要給出一些具體的,但是編譯器上沒有依據挑一個,這樣它默認爲obj,這是不是你想要的。

開箱即用並沒有很好的方法,但您可以創建一個活動模式以簡化體驗:https://stackoverflow.com/a/2140485/82959

+1

「問題是,類型參數必須給定一些特定的值,但編譯器沒有選擇其中的基礎,因此它默認爲obj,這不是您想要的。」 這是我不明白的。 F#類型推斷通常不會像這樣工作。如果我定義了一個函數 let func(v:'a)= printfn「%A」v 那麼'a將不會被賦予某個特定值(obj),它將保持未綁定狀態,因爲格式指定「%A」不綁定到任何類型。爲什麼在其他情況下執行綁定? –

+1

而對於具有活動模式的解決方法的鏈接 - 看起來像我需要做這樣的事情。 –

+1

@VagifAbilov因爲printfn「%A」不關心賦予它的實際類型;它處理任何事情。在相反的類型測試中放入一個「約束」來具體測試一個具體類型'Message <'a>'不是一個具體的類型,函數也是通用的,因爲每次使用新的泛型參數(int,string,whatever)這個特定類型的代碼將被編譯,其中'a將被該類型有效地取代預期的具體類型('消息'或'消息)' – Sehnsucht

3

簡而言之,Message<_>並不意味着你期望它是通用的,這意味着編譯器可以自由地推斷出Message的泛型類型參數。在沒有其他限制的情況下,其結果是使其成爲obj。如果你做的功能一般也會發生這種情況 - 泛型類型參數的函數(以及由此延伸,以Message類型檢查)在調用點決定:

let handleMessage<'a> (message: obj) = 
    match message with 
    | :? Message<'a> -> printfn "Message" 
    | _ -> printfn "Not message" 

handleMessage <| Message 1  // Not message -> 'a inferred to be obj 
handleMessage<int> <| Message 1 // Message -> 'a inferred to be int 

你可能想要做的是什麼像這樣:

let handleMessage message = 
    let typ = message.GetType() 
    match typ with 
    | _ when typ.GetGenericTypeDefinition() = typedefof<Message<_>> -> printfn "Message" 
    | _ -> printfn "Not message" 

handleMessage <| Message 1 
handleMessage <| Message (1 :> obj) 

這裏不是做一個類型測試,而是檢查兩者的泛型類型定義。

+1

我也可以寫消息<'a>得到相同的結果 - 'a會匹配成爲obj,因爲儘管obj是所有類型的根,但它與F#模式匹配中的匹配並不匹配。 當然,反射總是可以提供解決方法,但是我希望將此選項作爲最後的手段,部分原因是效率低下,並且此代碼是日誌記錄功能的一部分,這使得其性能至關重要。 –

+0

你測過了嗎?這個函數做什麼很微不足道,爲什麼它會很慢。 – scrwtp

+0

這些特殊的反射方法不會減慢速度,但我簡化了我的情況 - 我不僅想匹配消息,還要綁定匹配結果。通用的Message類型實際上是一個元組的包裝,我需要提取這些元組的值。所有這些使用反射。 –