在F#中,有可能有一个推测目标类型的tryParse函数 [英] In F#, is it possible to have a tryParse function that infers the target type

查看:268
本文介绍了在F#中,有可能有一个推测目标类型的tryParse函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目前我们这样做...

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

是否可以这样做...

  let d:DateTime = tryParse DateTime.MinValue2015.05.01


解决方案

是的。欢迎来到成员约束,ref和byref值的世界。

  let inline tryParseWithDefault 
defaultVal
文本
:^ a当^ a:(静态成员TryParse:string * ^ a byref - > bool)
=
let r = ref defaultVal
if (static member TryParse:string * ^ a byref - > bool)(text,& r.contents))
then!r
else defaultVal
/ pre>


  1. defaultVal 文本是形式参数,将被推断。这里, text 已经被约束为 string ,因为它被用作调用静态方法的第一个参数, SomeType.TryParse ,如稍后解释。

  2. 静态解析类型参数(对于形式'a 的通用类型参数)。 ^ a 将在编译时解析为特定类型。因此,托管它的函数必须标记为 inline ,这意味着函数的每次调用都将成为该调用与函数的实际主体的就地替换,其中每个静态类型参数将变为特定类型;在这种情况下,无论 defaultVal 是什么类型。没有限制 defaultVal 的可能类型的基本类型或接口类型约束。但是,您可以提供静态和实例成员约束,如在这里完成。具体来说,结果值(因此 defaultVal )显然必须有一个名为 TryParse 的静态成员, string ,引用该类型的可变实例,并返回 boolean 值。这个约束通过在开始的行上规定的返回类型的规定来表示:^ a when ... 。事实上, defaultVal 本身是一个可能的结果限制它是与 ^ a 相同的类型。 (约束也隐含在整个函数的其他地方)。

  3. :^ a当^ a:(static .... 结果类型 ^ a ,具有 string * ^ a的静态成员TryParse >。这就是说,结果类型将有一个静态成员接受一个 string ,一个对自身的实例的引用(因此mutable),并将返回一个 boolean value。这个描述是如何F#匹配TryParse的DateTime,Int32,TimeSpan等类型的.Net定义注意, byref 是F#等效于C#的 out ref 参数修饰符。

  4. let r = ref defaultVal 创建一个引用类型,并将所提供的值 defaultVal code> ref 是F#创建可变类型的方法之一,另一种是使用 mutable 关键字,它在堆栈上的值,而ref将其存储在主存储器/堆中,并且保持其在堆栈上的地址。

  5. if(^ a()可以使用下面的命令: :(static ... 是对静态推断类型的TryParse方法的调用结果语句 ^ a 。这个TryParse被传递(text,& r.contents)这里,& r.contents 提供对 r (text,& r.contents)

  6. !r 是如何读取参考值。 r.Value 也可以工作。

.Net提供的TryParse 方法似乎总是为out参数设置一个值。因此,不严格要求默认值。但是,您需要一个结果值持有者 r ,它必须具有初始值,甚至为null。我不喜欢null。另一个选择当然是对 ^ a 强制要求一个默认值属性的另一个约束。



以下后续解决方案通过使用 Unchecked.defaultof< ^ a> 从推断结果类型派生一个合适的占位符值(是的,感觉像魔法)。它还使用 Option 类型来表示获取结果值的成功和失败。因此,结果类型为 ^ a option

  tryParse 
text
:^当^ a:(静态成员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 我来了解这是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
b $ b let inline tryParse text = tryParseWithDefault(Unchecked.defaultof< _>)text


Presently we do this...

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

Is it possible to do this...

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

解决方案

Yes. Welcome to the world of member constraints, ref, and byref values.

  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. defaultVal and text are formal parameters and will be inferred. Here, text is already constrained to be string because it is used as the first parameter in a call to the static method, SomeType.TryParse, as explain later.
  2. ^a is a statically resolved type parameter (vs a generic type parameter of the form 'a). ^a will be resolved at compile time to a specific type. Consequently, the function hosting it must be marked inline, which means that each invocation of the function will become an in-place replacement of that invocation with the actual body of the function, wherein each static type parameter will become a specific type; in this case, whatever type defaultVal is. There is no base type or interface type constraints restricting the possible type of defaultVal. However, you can provide static and instance member constraints such as is done here. Specifically, the result value (and therefore defaultVal) must apparently have a static member called, TryParse, that both accepts a string, a reference to a mutable instance of that type, and return a boolean value. This constraint is made explicit by the stipulation of the stated return type on the line beginning with : ^a when .... The fact that defaultVal itself is a possible result constrains it to be of the same type as ^a. (The constraint is also implicit elsewhere throughout the function).
  3. : ^a when ^a : (static .... describes the result type, ^a, as having a static member called TryParse of type string * ^a byref -> bool. That is to say, the result type will have a static member that accepts a string, a reference to an instance of itself (and therefore mutable), and will return a boolean value. This description is how F# matches the .Net definition of TryParse on DateTime, Int32, TimeSpan, etc. types. Note, byref is F# equivalent of C#'s out or ref parameter modifier.
  4. let r = ref defaultVal creates a reference type and copies the provided value, defaultVal, into it. ref is one of the ways F# creates mutable types. The other is with the mutable keyword. The difference is that mutable stores its value on the stack while ref stores its in main memory/heap and holds an address (on the stack) to it. The latest version of F# will seek to automatically upgrade mutable designations to ref depending on the context, allowing you to code only in terms of mutable.
  5. if (^a : (static... is an if statement over the invocation results of the TryParse method on the statically inferred type, ^a. This TryParse is passed, (text, &r.contents), per its (string * ^a byref) signature. Here, &r.contents provides the reference to the mutable content of r (simulating C#'s out or ref parameter) per the expectation of TryParse. Note, we are off the reservation here and certain F# niceties for inter-operating with the .Net framework do not extend out this far, in particular, the automatic rolling up of space separated F# parameters into .net framework function parameters as a tuple. Hence, the parameters are provide to the function as tuple, (text, &r.contents).
  6. !r is how you read a reference value. r.Value would also work.

The TryParse methods provided by .Net seems to always set a value for the out parameter. Consequently, a default value is not strictly required. However, you need a result value holder, r, and it must have an initial value, even null. I didn't like null. Another option, of course, is to impose another constraint on ^a that demands a default value property of some sort.

The following subsequent solution removes the need for a default parameter by using the Unchecked.defaultof< ^a > to derive a suitable placeholder value from the "inferred result" type (yes, it feels like magic). It also uses the Option type to characterize success and failure obtaining a result value. The result type is therefore, ^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

And, per @kvb suggestions, the following brevity is possible. In this case, type inference is employed to both stipulate the type constraint on ^a as a consequence of it's invocation in the if (^a : ...)) expression and to also establish the type of the mutable buffer r for TryParse's out parameter. 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

这篇关于在F#中,有可能有一个推测目标类型的tryParse函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆