目前我們做這個的TryParse函數...在F#中,是有可能有一個推斷出目標類型
let parseDate defaultVal text =
match DateTime.TryParse s with
| true, d -> d
| _ -> defaultVal
是否有可能做到這一點...
let d : DateTime = tryParse DateTime.MinValue "2015.05.01"
目前我們做這個的TryParse函數...在F#中,是有可能有一個推斷出目標類型
let parseDate defaultVal text =
match DateTime.TryParse s with
| true, d -> d
| _ -> defaultVal
是否有可能做到這一點...
let d : DateTime = tryParse DateTime.MinValue "2015.05.01"
是。歡迎來到成員約束,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
defaultVal
和text
是形式參數,將被推斷。這裏,text
已經被約束爲string
,因爲它被用作靜態方法的調用中的第一個參數,如後面所解釋的SomeType.TryParse
。^a
是靜態解析的類型參數(與形式爲'a
的通用類型參數相比)。 ^a
將在編譯時解析爲特定類型。因此,託管它的函數必須標記爲inline
,這意味着函數的每次調用將成爲該函數的實際主體的就地替換,其中每個靜態類型參數將變爲特定類型;在這種情況下,不管什麼類型defaultVal
是。沒有限制defaultVal
的可能類型的基本類型或接口類型限制。但是,您可以提供靜態和實例成員約束,如在此處完成。具體而言,結果值(因此defaultVal
)必須顯然具有一個名爲TryParse
的靜態成員,它們都接受string
(對該類型的可變實例的引用),並返回boolean
值。該限制通過規定: ^a when ...
開始的行上規定的返回類型來明確。 defaultVal
本身是可能的結果這一事實限制它與^a
的類型相同。 (約束在整個函數的其他地方也是隱含的)。: ^a when ^a : (static ....
將結果類型^a
描述爲具有名爲TryParse的類型爲string * ^a byref -> bool
的靜態成員。也就是說,結果類型將有一個靜態成員,該成員接受string
(對自身的一個實例的引用(因此是可變的)),並返回boolean
值。此描述是F#如何匹配DateTime,Int32,TimeSpan等類型上的TryParse的.Net定義。請注意,byref
是C#的out
或ref
參數修飾符的F#等效值。let r = ref defaultVal
創建參考類型並將提供的值defaultVal
複製到其中。 ref
是F#創建可變類型的方法之一。另一種是mutable
關鍵字。不同之處在於mutable將其值存儲在堆棧中,而ref則將其存儲在主內存/堆棧中,並在堆棧中保存一個地址(堆棧)。最新版本的F#將嘗試根據上下文自動升級可變指定以允許您僅根據可變內容進行編碼。if (^a : (static...
是一個if
聲明,它是靜態推斷類型的TryParse方法的調用結果^a
。該TryParse按其(string * ^a byref)
簽名通過,(text, &r.contents)
。這裏,&r.contents
提供了r
(模擬C#的out
或ref
參數)的可變內容的參考,以期望TryParse。請注意,這裏我們沒有提到這個保留,而與.Net框架互操作的某些F#細節並沒有擴展到這個範圍,特別是將F#參數分隔成的空間自動捲起。網絡框架函數參數作爲元組。因此,參數作爲元組提供給函數(text, &r.contents)
。!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
注:
self
對象通常是隱藏的第一參數'b
樣品的使用將是以下:
let button : Button = window?myButton
let report : ReportViewer = window?reportViewer1
在F#+中,該函數的定義方式類似,部分版本爲'parse' https://github.com/gmpl/FSharpPlus/blob/24f6501d7c6449e0eb06c993ad247c8f1db5a3f6/FSharpPlus/Operators.fs#L261 – Gustavo
作爲次要樣式註釋,使用'let mutable x = Unchecked.defaultof <_>'然後使用'&x'作爲方法調用的參數似乎比引入實際的'ref'值更清晰;同樣,簽名可以從定義中推斷出來(所以你不必寫出約束兩次),但也許你是出於教學原因包含它的。 – kvb
@Gustavo我不知道FSharpPlus項目,只是傳遞了FsControl。謝謝你打開我的眼睛。他們確實定義了TryParse是一個類似但更優雅的方式:) https://github.com/gmpl/FsControl/blob/dc8b41a5b7f50f5adc419512df8efce0801c4351/FsControl.Core/Converter.fs#L86 – George
參見HTTP://計算器。 COM/q /82959分之4656864。 – kvb