在F#中,是否可以使用tryParse函数来推断目标类型

时间:2015-10-16 01:44:04

标签: f# constraints inline string-parsing

目前我们这样做......

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

是否可以这样做......

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

1 个答案:

答案 0 :(得分: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本身是可能的结果这一事实将其约束为与: ^a when ^a : (static ....相同的类型。 (约束也隐含在整个函数的其他地方)。
  3. ^a将结果类型string * ^a byref -> bool描述为具有名为TryParse且类型为string的静态成员。也就是说,结果类型将具有接受boolean的静态成员,对自身实例的引用(因此是可变的),并将返回byref值。此描述是F#如何匹配DateTime,Int32,TimeSpan等类型的TryParse的.Net定义。请注意,out是F#的等效于C#的reflet r = ref defaultVal参数修饰符。
  4. defaultVal创建引用类型,并将提供的值ref复制到其中。 mutable是F#创建可变类型的方法之一。另一个是if (^a : (static...关键字。不同之处在于mutable将其值存储在堆栈上,而ref将其存储在主内存/堆中并保存一个地址(在堆栈上)。最新版本的F#将根据上下文自动将可变指定升级为ref,允许您仅根据可变性进行编码。
  5. if是关于静态推断类型^a上的TryParse方法的调用结果的(text, &r.contents)语句。此TryParse按其(string * ^a byref)签名传递&r.contents。在此,r根据TryParse的预期提供out(模拟C#的ref(text, &r.contents)参数)的可变内容的引用。注意,我们在这里没有预留,并且与.Net框架互操作的某些F#niceties没有扩展到这一点,特别是将空间分离的F#参数自动汇总到.net框架函数参数作为元组。因此,参数作为元组!r
  6. 提供给函数
  7. r.Value是您阅读参考值的方式。 TryParse也可以。
  8. .Net提供的r方法似乎总是为out参数设置一个值。因此,不严格要求默认值。但是,您需要一个结果值保持器^a,并且它必须具有初始值,即使为null。我不喜欢null。当然,另一个选择是对需要某种默认值属性的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

    ^a

    而且,根据@kvb建议,以下简洁是可能的。在这种情况下,类型推断用于规定if (^a : ...))上的类型约束,因为它在r表达式中被调用,并且还建立了可变缓冲区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 的类型。 TryParse的out参数。 I have since come to learn this is how FsControl does some of it's magic

    ?

    对于在实例成员上使用类型约束的情况,例如类型约束fsharp的动态成员查找自定义运算符FindName:string->obj,使得主题的类型必须包含let inline (?) (instanceObj:^A) (property:string) : 'b = (^A : (member FindName:string -> obj) (instanceObj, property)) :?> 'b 成员,语法为如下:

    self

    注意:

    1. 实例方法的实际签名显式指定'b对象,该对象通常是隐藏的第一个参数
    2. 此解决方案还会将结果推广到let button : Button = window?myButton let report : ReportViewer = window?reportViewer1
    3. 示例用法如下:

      {{1}}