【问题标题】:In F#, is it possible to have a tryParse function that infers the target type在 F# 中,是否可以有一个推断目标类型的 tryParse 函数
【发布时间】:2016-01-14 15:52:32
【问题描述】:

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

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

这样可以吗...

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

【问题讨论】:

标签: f# constraints inline string-parsing


【解决方案1】:

是的。欢迎来到成员约束、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 的第一个参数,稍后会解释。 defaultVal 被限制为 ^a 的任何值,因为它是每个 if..then..else 表达式的可能结果值。
  2. ^a 是一个静态解析的类型参数(相对于'a 形式的泛型类型参数)。特别是,^a 将在编译时解析为特定类型。因此,托管它的函数必须标记为inline,这意味着函数的每次调用都将成为该函数体的就地替换,其中每个静态类型参数都将成为特定类型;在这种情况下,无论defaultVal 是什么类型。没有限制defaultVal 的可能类型的基本类型或接口类型约束。但是,您可以提供静态和实例成员约束,例如此处所做的。具体来说,结果值(以及defaultVal 的类型)显然必须有一个名为TryParse 的静态成员,它接受string 和对该类型可变实例的引用,并返回boolean价值。此约束由以: ^a when ... 开头的行上声明的返回类型明确表示。 defaultVal 本身是一个可能的结果这一事实将其限制为与 ^a 相同的类型。 (该约束也隐含在整个函数的其他地方,如下所述,这是不必要的)。
  3. : ^a when ^a : (static .... 将结果类型 ^a 描述为具有类型为 string * ^a byref -> bool 的名为 TryParse 的静态成员。也就是说,结果类型必须有一个名为TryParse 的静态成员,它接受一个string,一个对自身实例的引用(因此是一个可变实例),并将返回一个boolean 值。此描述是 F# 如何在 DateTime、Int32、TimeSpan 等类型上匹配 TryParse 的 .Net 定义。请注意,byref 是 F# 等效于 C# 的 outref 参数修饰符。
  4. let r = ref defaultVal 创建一个引用类型并将提供的值 defaultVal 复制到其中。 ref 是 F# 创建可变类型的方式之一。另一个是mutable 关键字。不同之处在于 mutable 将其值存储在堆栈中,而 ref 将其存储在主内存/堆中并保存一个地址(在堆栈上)。最新版本的 F# 将寻求根据上下文自动将可变指定升级为 ref,从而允许您仅根据可变编码进行编码。
  5. if (^a : (static... 是针对静态推断类型^a 上的 TryParse 方法的调用结果的 if 语句。此 TryParse 根据其 (string * ^a byref) 签名传递,(text, &r.contents)。这里,&r.contents 根据 TryParse 的期望提供了对 r 可变内容的引用(模拟 C# 的 outref 参数)。请注意,我们在这里没有保留,与 .Net 框架互操作的某些 F# 细节并没有延伸到这么远;特别是,将空格分隔的 F# 参数作为元组自动汇总为 .net 框架函数参数是不可用的。因此,参数作为元组(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 tryParse text : ^a option = 
  let mutable r = Unchecked.defaultof<_>
  if (^a : (static member TryParse: string * ^a byref -> bool) (text, &r)) 
  then Some r
  else None

let inline tryParseWithDefault defaultVal text : ^a = 
  match tryParse text with
  | Some d -> d
  | _      -> defaultVal

其中的用法是……

> let x:DateTime option = tryParse "December 31, 2014";;
val x : DateTime option = Some 2014-12-31 12:00:00 a.m.

> let x:bool option = tryParse "false";;
val x : bool option = Some false

> let x:decimal option = tryParse "84.32";;    
val x : decimal option = Some 84.32M

对于对实例成员使用类型约束的情况,例如 Fsharp 的动态成员查找运算符 ? 的类型约束,这样其目标的类型必须包含 FindName:string -&gt; obj 成员以用于解析成员查找请求,语法如下:

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

注意:

  1. 实例方法的签名必须明确指定它们的self对象,这通常是对象方法的隐藏第一个参数
  2. 此解决方案还将结果提升为'b 的类型

示例用法如下:

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

【讨论】:

  • 在 F#+ 中是以类似方式定义的函数以及部分版本 parse github.com/gmpl/FSharpPlus/blob/…
  • 作为次要风格的注释,使用let mutable x = Unchecked.defaultof&lt;_&gt; 然后使用&amp;x 作为方法调用的参数对我来说似乎比引入实际的ref 值更干净;此外,可以从定义中推断出签名(因此您不必两次写出约束),尽管您可能出于教学原因将其包括在内。
  • @Gustavo 我不知道 FSharpPlus 项目,只知道 FsControl。谢谢你让我大开眼界。他们确实定义 TryParse 是一种类似但更优雅的方式 :) github.com/gmpl/FsControl/blob/…
  • @kvb 感谢您的洞察力。使用成员约束来调用方法(静态和实例)在 MSDN 材料中对我来说并不是很明显。当我发现它时,我很惊讶。我添加了一个包含您的“风格”建议的示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-17
相关资源
最近更新 更多