2015-10-16 26 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

相关问题