2015-10-16 27 views
5

目前我們做這個的TryParse函數...在F#中,是有可能有一個推斷出目標類型

let parseDate defaultVal text = 
match DateTime.TryParse s with 
| true, d -> d 
| _  -> defaultVal 

是否有可能做到這一點...

let d : DateTime = tryParse DateTime.MinValue "2015.05.01" 
+0

參見HTTP://計算器。 COM/q /82959分之4656864。 – kvb

回答

10

是。歡迎來到成員約束,ref和byref值的世界。

let inline tryParseWithDefault 
     defaultVal 
     text 
     : ^a when ^a : (static member TryParse : string * ^a byref -> bool) 
     = 
    let r = ref defaultVal 
    if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents)) 
    then !r 
    else defaultVal 
  1. defaultValtext是形式參數,將被推斷。這裏,text已經被約束爲string,因爲它被用作靜態方法的調用中的第一個參數,如後面所解釋的SomeType.TryParse
  2. ^a是靜態解析的類型參數(與形式爲'a的通用類型參數相比)。 ^a將在編譯時解析爲特定類型。因此,託管它的函數必須標記爲inline,這意味着函數的每次調用將成爲該函數的實際主體的就地替換,其中每個靜態類型參數將變爲特定類型;在這種情況下,不管什麼類型defaultVal是。沒有限制defaultVal的可能類型的基本類型或接口類型限制。但是,您可以提供靜態和實例成員約束,如在此處完成。具體而言,結果值(因此defaultVal)必須顯然具有一個名爲TryParse的靜態成員,它們都接受string(對該類型的可變實例的引用),並返回boolean值。該限制通過規定: ^a when ...開始的行上規定的返回類型來明確。 defaultVal本身是可能的結果這一事實限制它與^a的類型相同。 (約束在整個函數的其他地方也是隱含的)。
  3. : ^a when ^a : (static ....將結果類型^a描述爲具有名爲TryParse的類型爲string * ^a byref -> bool的靜態成員。也就是說,結果類型將有一個靜態成員,該成員接受string(對自身的一個實例的引用(因此是可變的)),並返回boolean值。此描述是F#如何匹配DateTime,Int32,TimeSpan等類型上的TryParse的.Net定義。請注意,byref是C#的outref參數修飾符的F#等效值。
  4. let r = ref defaultVal創建參考類型並將提供的值defaultVal複製到其中。 ref是F#創建可變類型的方法之一。另一種是mutable關鍵字。不同之處在於mutable將其值存儲在堆棧中,而ref則將其存儲在主內存/堆棧中,並在堆棧中保存一個地址(堆棧)。最新版本的F#將嘗試根據上下文自動升級可變指定以允許您僅根據可變內容進行編碼。
  5. if (^a : (static...是一個if聲明,它是靜態推斷類型的TryParse方法的調用結果^a。該TryParse按其(string * ^a byref)簽名通過,(text, &r.contents)。這裏,&r.contents提供了r(模擬C#的outref參數)的可變內容的參考,以期望TryParse。請注意,這裏我們沒有提到這個保留,而與.Net框架互操作的某些F#細節並沒有擴展到這個範圍,特別是將F#參數分隔成的空間自動捲起。網絡框架函數參數作爲元組。因此,參數作爲元組提供給函數(text, &r.contents)
  6. !r是您如何閱讀參考值。 r.Value也可以工作。

.Net提供的TryParse方法似乎總是爲out參數設置一個值。因此,默認值不是嚴格要求的。但是,您需要結果值持有者r,並且它必須具有初始值,甚至爲空。我不喜歡null。當然,另一種選擇是對^a施加另一個約束,該約束要求某種默認值屬性。

以下後續解決方案通過使用Unchecked.defaultof<^a>從「推測結果」類型(是的,感覺像魔術)中派生出合適的佔位符值,從而消除了對默認參數的需要。它還使用Option類型來表徵獲得結果值的成功和失敗。結果類型因此是^a option

tryParse 
    text 
    : ^a option when ^a : (static member TryParse : string * ^a byref -> bool) 
    = 
    let r = ref Unchecked.defaultof<^a> 
    if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r.contents)) 
    then Some (!r) 
    else None 

而且,根據@kvb建議,下列簡潔是可能的。在這種情況下,使用類型推斷來規定^a的類型約束,因爲它在if (^a : ...))表達式中被調用,並且還爲TryParse的out參數建立了可變緩衝區r的類型。 I have since come to learn this is how FsControl does some of it's magic

let inline tryParseWithDefault defaultVal text : ^a option = 
    let mutable r = defaultVal 
    if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r)) 
    then Some r 
    else None 

let inline tryParse text = tryParseWithDefault (Unchecked.defaultof<_>) text 

對於使用實例成員上類型約束,諸如類型約束fsharp的動態成員查找定製操作者,?,使得被攝體的類型必須包含FindName:string->obj構件,所述語法如下的情況:

let inline (?) (instanceObj:^A) (property:string) : 'b = 
    (^A : (member FindName:string -> obj) (instanceObj, property)) :?> 'b 

注:

  1. 的實例方法的實際簽名明確指定self對象通常是隱藏的第一參數
  2. 該解決方案還促進無論結果,'b

樣品的使用將是以下:

let button : Button = window?myButton 
let report : ReportViewer = window?reportViewer1 
+1

在F#+中,該函數的定義方式類似,部分版本爲'parse' https://github.com/gmpl/FSharpPlus/blob/24f6501d7c6449e0eb06c993ad247c8f1db5a3f6/FSharpPlus/Operators.fs#L261 – Gustavo

+0

作爲次要樣式註釋,使用'let mutable x = Unchecked.defaultof <_>'然後使用'&x'作爲方法調用的參數似乎比引入實際的'ref'值更清晰;同樣,簽名可以從定義中推斷出來(所以你不必寫出約束兩次),但也許你是出於教學原因包含它的。 – kvb

+0

@Gustavo我不知道FSharpPlus項目,只是傳遞了FsControl。謝謝你打開我的眼睛。他們確實定義了TryParse是一個類似但更優雅的方式:) https://github.com/gmpl/FsControl/blob/dc8b41a5b7f50f5adc419512df8efce0801c4351/FsControl.Core/Converter.fs#L86 – George

相關問題