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

Geo*_*rge 5 f# inline constraints string-parsing

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

let parseDate defaultVal text = 
match DateTime.TryParse s with
| true, d -> d
| _       -> defaultVal
Run Code Online (Sandbox Code Playgroud)

是否有可能做到这一点...

let d : DateTime = tryParse DateTime.MinValue "2015.05.01"
Run Code Online (Sandbox Code Playgroud)

Geo*_*rge 11

是.欢迎来到成员约束,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
Run Code Online (Sandbox Code Playgroud)
  1. defaultVal并且text是正式参数,将被推断.在这里,text已经被约束为string因为它被用作对静态方法的调用中的第一个参数SomeType.TryParse,如稍后解释的那样.
  2. ^a是静态解析的类型参数(与表单的泛型类型参数相对'a).^a将在编译时解析为特定类型.因此,必须标记托管它的函数inline,这意味着函数的每次调用将成为函数的实际主体的就地调用的就地替换,其中每个静态类型参数将成为特定类型; 在这种情况下,无论什么类型defaultVal.没有基本类型或接口类型约束限制可能的类型defaultVal.但是,您可以提供静态和实例成员约束,例如此处所做的.具体来说,结果值(因此defaultVal)显然必须有一个名为的静态成员TryParse,它们都接受astring,对该类型的可变实例的引用,并返回一个boolean值.这个约束通过在开头的行上规定的返回类型来明确: ^a when ....事实上,defaultVal它本身就是一个可能的结果,它将它限制为与它相同的类型^a.(约束也在整个函数的其他地方隐含).
  3. : ^a when ^a : (static ....将结果类型描述^a为具有名为TryParse类型的静态成员string * ^a byref -> bool.也就是说,结果类型将具有一个静态成员,该成员接受一个string对其自身实例的引用(因此是可变的),并将返回一个boolean值.此描述是F#如何匹配DateTime,Int32,TimeSpan等类型的TryParse的.Net定义.注意,byrefF#等价于C#outref参数修饰符.
  4. let r = ref defaultVal创建引用类型并将提供的值复制defaultVal到其中.ref是F#创建可变类型的方法之一.另一个是mutable关键字.不同之处在于,mutable将其值存储在堆栈上,而ref将其存储在主内存/堆中并保存一个地址(在堆栈上).最新版本的F#将根据上下文自动将可变指定升级为ref,允许您仅根据可变性进行编码.
  5. if (^a : (static...if关于静态推断类型的TryParse方法的调用结果的声明,^a.(text, &r.contents)根据(string * ^a byref)签名传递此TryParse .这里,根据TryParse的期望&r.contents提供对r(模拟C#outref参数)的可变内容的引用.注意,我们在这里没有预留,并且与.Net框架互操作的某些F#niceties没有扩展到这一点,特别是将空间分离的F#参数自动汇总到.net框架函数参数作为元组.因此,参数作为元组提供给函数(text, &r.contents).
  6. !r是你如何阅读参考值.r.Value也会有用.

TryParse.Net提供的方法似乎总是为out参数设置一个值.因此,不严格要求默认值.但是,您需要一个结果值持有者,r并且它必须具有初始值,甚至为null.我不喜欢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
Run Code Online (Sandbox Code Playgroud)

而且,根据@kvb建议,以下简洁是可能的.在这种情况下,类型推断用于规定类型约束,^a因为它在if (^a : ...))表达式中被调用,并且还r为TryParse的out参数建立可变缓冲区的类型.我已经开始了解这就是FsControl如何做到这一点的神奇之处

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
Run Code Online (Sandbox Code Playgroud)

对于在实例成员上使用类型约束的情况,例如类型约束fsharp的动态成员查找自定义运算符?,使得主题的类型必须包含FindName:string->obj成员,语法如下:

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

注意:

  1. 实例方法的实际签名显式指定self通常是隐藏的第一个参数的对象
  2. 这个解决方案也促进了结果, 'b

示例用法如下:

let button : Button = window?myButton
let report : ReportViewer = window?reportViewer1
Run Code Online (Sandbox Code Playgroud)