问题描述
我很困惑如何在没有像 ('a -> 'a)
I'm confused on how to label a function as generic without an explicit type declaration like ('a -> 'a)
let add a b = a + b
这给了我们
val add : a:int -> b:int -> int
但是我们可以立即调用
add "Hello " "World!"
现在 add 的值是
val add : a:string -> b:string -> string
val it : string = "Hello World!"
如果我们再调用
add 2 3 // then we get
error: This expression was expected to have type string but here has type int
我如何确保一个函数适用于所有声明了函数 (+)
定义的类型
How do I ensure that a function works on all types that say have the function (+)
defined
推荐答案
这是 F# 在壁橱中令人尴尬的骨架.
This is F#'s embarrassing skeleton in the closet.
试试这个:
> let mapPair f (x,y) = (f x, f y)
val mapPair : f:('a -> 'b) -> x:'a * y:'a -> 'b * 'b
完全通用!显然,函数应用程序和元组是有效的.
Fully generic! Clearly, function application and tuples work.
现在试试这个:
> let makeList a b = [a;b]
val makeList : a:'a -> b:'a -> 'a list
嗯,也很一般.这个怎么样:
Hmmm, also generic. How about this:
> let makeList a b = [a + b]
val makeList : a:int -> b:int -> int list
啊哈,只要我在里面有一个 (+)
,它就会因为某种原因变成 int
.
让我们继续玩:
Aha, as soon as I have a (+)
in there, it becomes int
for some reason.
Let's keep playing:
> let inline makeList a b = [a + b]
val inline makeList :
a: ^a -> b: ^b -> ^c list
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
嗯,有趣.事实证明,如果我让函数 inline
,那么 F#确实 认为它是通用的,但它也给了它这个奇怪的 when
子句,而我的通用参数有这个奇怪的 ^
符号,而不是通常的勾号.
这种奇怪的语法称为静态解析类型参数"(参见 here 有一些连贯的解释),基本思想是函数 (+)
要求它的参数有一个 静态成员(+)
定义.我们来验证一下:
Hmmm, interesting. Turns out, if I make the function inline
, then F# does consider it generic, but it also gives it this weird when
clause, and my generic parameters have this strange ^
symbol instead of the usual tick.
This strange syntax is called "statically resolved type parameters" (see here for a somewhat coherent explanation), and the basic idea is that the function (+)
requires its arguments to have a static member (+)
defined. Let's verify:
> let x = 0 :> obj
let y = 0 :> obj
let z = x + y
Script1.fsx(14,13): error FS0001: The type 'obj' does not support the operator '+'
> type My() =
static member (+)( a:My, b:My ) = My()
let x = My()
let y = My()
let z = x + y
val x : My
val y : My
val z : My
现在,问题在于 CLR 不支持这种泛型参数(即任何类型,只要它有这样那样的成员"),所以 F# 必须伪造它并在编译时解决这些调用.但正因如此,任何使用此功能的方法都无法编译为真正的通用 IL 方法,因此必须是单态的(由 inline
启用).
Now, the problem with this is that CLR does not support this kind of generic parameters (i.e. "any type, as long as it has such and such members"), so F# has to fake it and resolve these calls at compile time. But because of this, any methods that use this feature cannot be compiled to true generic IL methods, and thus have to be monomorphised (which is enabled by inline
).
但是,要求每个使用算术运算符的函数都声明为 inline
会很不方便,不是吗?因此,F# 又做了一个额外的步骤,并尝试根据稍后在代码中实例化这些静态解析的泛型参数的方式来修复这些参数.这就是为什么你的函数一旦与 string
一起使用就会变成 string->string->string
.
But then, it would be very inconvenient to require that every function that uses arithmetic operators be declared inline
, wouldn't it? So F# goes yet another extra step and tries to fix these statically resolved generic parameters based on how they are instantiated later in the code. That's why your function turns into string->string->string
as soon as you use it with a string
once.
但是如果您将函数标记为 inline
,F# 就不必修复参数,因为它不必将函数编译为 IL,因此您的参数保持不变:
But if you mark your function inline
, F# wouldn't have to fix parameters, because it wouldn't have to compile the function down to IL, and so your parameters remain intact:
> let inline add a b = a + b
val inline add :
a: ^a -> b: ^b -> ^c
when ( ^a or ^b) : (static member ( + ) : ^a * ^b -> ^c)
这篇关于F# 泛型/函数重载语法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,WP2