在F#中,有可能有一个推测目标类型的tryParse函数 [英] In F#, is it possible to have a tryParse function that infers the target type
问题描述
目前我们这样做...
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
/ pre>
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
defaultVal
和文本
是形式参数,将被推断。这里,text
已经被约束为string
,因为它被用作调用静态方法的第一个参数,SomeType.TryParse
,如稍后解释。
静态解析类型参数(对于形式
'a
的通用类型参数)。^ a
将在编译时解析为特定类型。因此,托管它的函数必须标记为inline
,这意味着函数的每次调用都将成为该调用与函数的实际主体的就地替换,其中每个静态类型参数将变为特定类型;在这种情况下,无论defaultVal
是什么类型。没有限制defaultVal
的可能类型的基本类型或接口类型约束。但是,您可以提供静态和实例成员约束,如在这里完成。具体来说,结果值(因此defaultVal
)显然必须有一个名为TryParse
的静态成员,string
,引用该类型的可变实例,并返回boolean
值。这个约束通过在开始的行上规定的返回类型的规定来表示:^ a when ...
。事实上,defaultVal
本身是一个可能的结果限制它是与^ a
相同的类型。 (约束也隐含在整个函数的其他地方)。
:^ a当^ a:(static ....
结果类型^ a
,具有string * ^ a的静态成员TryParse >。这就是说,结果类型将有一个静态成员接受一个
string
,一个对自身的实例的引用(因此mutable),并将返回一个boolean
value。这个描述是如何F#匹配TryParse的DateTime,Int32,TimeSpan等类型的.Net定义注意,byref
是F#等效于C#的
out
或ref
参数修饰符。
let r = ref defaultVal
创建一个引用类型,并将所提供的值defaultVal
code> ref 是F#创建可变类型的方法之一,另一种是使用mutable
关键字,它在堆栈上的值,而ref将其存储在主存储器/堆中,并且保持其在堆栈上的地址。
if(^ a()可以使用下面的命令: :(static ... 是对静态推断类型的TryParse方法的调用结果
的
语句
^ a
。这个TryParse被传递(text,& r.contents)
这里,& r.contents
提供对r $的可变内容的引用注意,我们是关闭的,因为我们可以通过使用一个简单的方法来创建一个简单的C#保留在这里和某些F#nicies与.Net框架互操作不扩展到这一点,特别是空间分离的F#参数自动卷起成.net框架函数参数作为元组,因此,参数提供到
(text,& r.contents)
。
!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
defaultVal
andtext
are formal parameters and will be inferred. Here,text
is already constrained to bestring
because it is used as the first parameter in a call to the static method,SomeType.TryParse
, as explain later.^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 markedinline
, 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 typedefaultVal
is. There is no base type or interface type constraints restricting the possible type ofdefaultVal
. However, you can provide static and instance member constraints such as is done here. Specifically, the result value (and thereforedefaultVal
) must apparently have a static member called,TryParse
, that both accepts astring
, a reference to a mutable instance of that type, and return aboolean
value. This constraint is made explicit by the stipulation of the stated return type on the line beginning with: ^a when ...
. The fact thatdefaultVal
itself is a possible result constrains it to be of the same type as^a
. (The constraint is also implicit elsewhere throughout the function).: ^a when ^a : (static ....
describes the result type,^a
, as having a static member called TryParse of typestring * ^a byref -> bool
. That is to say, the result type will have a static member that accepts astring
, a reference to an instance of itself (and therefore mutable), and will return aboolean
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#'sout
orref
parameter modifier.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 themutable
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.if (^a : (static...
is anif
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 ofr
(simulating C#'sout
orref
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)
.!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 theOption
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 theif (^a : ...))
expression and to also establish the type of the mutable bufferr
for TryParse's out parameter. I have since come to learn this is how FsControl does some of it's magiclet 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屋!