具有泛型参数类型的函数

Posted

技术标签:

【中文标题】具有泛型参数类型的函数【英文标题】:Functions with generic parameter types 【发布时间】:2010-10-04 19:17:40 【问题描述】:

我试图弄清楚如何定义一个适用于多种类型参数(例如 int 和 int64)的函数。据我了解,在 F# 中函数重载是不可能的(当然编译器会抱怨)。以下面的函数为例。

let sqrt_int = function
    | n:int   -> int (sqrt (float n))
    | n:int64 -> int64 (sqrt (float n))

编译器当然会抱怨语法无效(似乎不支持模式匹配中的类型约束),尽管我认为这说明了我想要实现的目标:对多个参数类型进行操作并返回值的函数的相应类型。我有一种感觉,在 F# 中使用泛型类型/类型推断/模式匹配的某种组合是可能的,但是语法让我无法理解。我也尝试过使用 :?运算符(动态类型测试)和模式匹配块中的 when 子句,但这仍然会产生各种错误。

由于我对这门语言比较陌生,我很可能在这里尝试做一些不可能的事情,所以如果有其他解决方案,请告诉我。

【问题讨论】:

【参考方案1】:

重载通常是类型推断语言的错误(至少当像 F# 一样,类型系统不足以包含类型类时)。在 F# 中有多种选择:

在方法(类型的成员)上使用重载,在这种情况下,重载的工作方式与其他 .Net 语言中的工作方式非常相似(您可以临时重载成员,前提是调用可以通过参数的数量/类型来区分) 使用“inline”、“^”和静态成员约束对函数进行临时重载(这是大多数需要处理 int/float/etc 的各种数学运算符;这里的语法很奇怪,除了 F# 库之外很少使用) 通过传递额外的操作字典参数来模拟类型类(这是 INumeric 在 F# PowerPack 库之一中所做的,用于为任意用户定义类型概括各种数学算法) 回退到动态类型(传入“obj”参数,进行动态类型测试,为错误类型抛出运行时异常)

对于您的特定示例,我可能只使用方法重载:

type MathOps =
    static member sqrt_int(x:int) = x |> float |> sqrt |> int
    static member sqrt_int(x:int64) = x |> float |> sqrt |> int64

let x = MathOps.sqrt_int 9
let y = MathOps.sqrt_int 100L

【讨论】:

@Jon Harrop:这个问题难道不是一个令人信服的例子吗?我预计现代语言会具有此功能。该函数重载应该在每个语言设计者列表中。这是可以解决的问题,但是为什么!它应该可以工作。 @Gorgen:我不相信,不。问题是这永远不会“正常工作”。在 C++ 和 C# 等语言中重载不利于类型推断。在像 Haskell 这样的语言中使用类型类等特性重载可能会在您最不期望的时候大大降低基本算术运算的性能。标准 ML 选择 ad-hoc 多态性来涵盖单个特殊情况(int vs float)但不涵盖其他情况(例如scalar vs vector)。 OCaml 根本没有重载,但最近采用了一种称为“定界”重载的解决方案。 Scala 和 C# 3 选择了“本地”类型推断的折衷方案,这只是蹩脚。 F# 选择了一些重载(算术运算符)的实用妥协,但在一个保证静态解析和无调度的框架中,它总是很快。这些权衡是已知的,但尚未解决。我个人偏爱 F# 解决方案,因为它解决了最重要的重载(算术),而不会在最重要的地方(算术!)引入糟糕的性能特征。 @Gorgen:没错。使用类型推断重载螺丝,因此 F# 将其降级到类型推断已经被拧紧的 OO 端。 多次分派并不意味着性能不佳。这是 Julia 的核心设计原则,以至于它的一位创建者说他们比任何其他非研究语言都走得更远。然而 Julia 主要是为高性能数值计算而设计的,并且通常优于 C。【参考方案2】:

这行得通:

type T = T with
    static member ($) (T, n:int  ) = int   (sqrt (float n))
    static member ($) (T, n:int64) = int64 (sqrt (float n))

let inline sqrt_int (x:'t) :'t = T $ x

它使用静态约束和重载,在编译时查找参数的类型。

静态约束是在存在操作符时自动生成的(在本例中为操作符$),但始终可以手动编写:

type T = T with
    static member Sqr (T, n:int  ) = int   (sqrt (float n))
    static member Sqr (T, n:int64) = int64 (sqrt (float n))

let inline sqrt_int (x:'N) :'N = ((^T or ^N) : (static member Sqr: ^T * ^N -> _) T, x)

更多关于这个here。

【讨论】:

这是对 Brian 和 Mauricio 提供的解决方案的一个非常好的改进,它具有一个没有点符号的函数的好处(比较 Brian 的),它增加了编译时类型检查(比较 Mauricio 的)。您是否愿意详细说明这是如何工作的以及是否需要运算符定义? 感谢@Abel,我确实详细说明了该解决方案,并包含了一个指向博客条目的链接,其中包含更多详细信息。 Mauricio 的答案采用了一种非常不同的方法,这也是有效的,它通过从float 转换为所有类型始终使用相同的代码,这是更少的代码,但如果你想使用大整数,你可能会遇到限制。 【参考方案3】:

是的,这可以做到。看看this hubFS thread。

在这种情况下,解决方案是:

let inline retype (x:'a) : 'b = (# "" x : 'b #)
let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'a

警告:没有编译时类型检查。 IE。 sqrt_int "blabla" 可以正常编译,但运行时会出现 FormatException。

【讨论】:

谢谢,这似乎是解决方案(尽管它不像我希望的那样简单)。只是为了澄清,我想要这样的东西?让内联 sqrt_int (n:^a) = retype (sqrt (float n)) : ^a 是的,这行得通。但是,请注意,这样做会丢失编译时类型检查。 IE。 sqrt_int "blabla" 类型检查,尽管您会在运行时收到 FormatException。 好的,所以在这种情况下使用帽子运算符真的没有意义,对吧?如果我碰巧在函数中(在强制转换之前)使用了诸如 * 之类的算术运算符,那会确保编译时检查吗? 是的,在这种情况下,普通类型参数也可以工作: let inline sqrt_int (n:'a) = retype (sqrt (float n)) : 'a 不确定算术运算符是什么意思. 那是什么(# "" x : 'b #) 野兽?编译器告诉我,该构造已被弃用,只能在 F# 库中使用。祝你好运搜索(#...【参考方案4】:

这是使用运行时类型检查的另一种方式...

let sqrt_int<'a> (x:'a) : 'a = // '
    match box x with
    | :? int as i -> downcast (i |> float |> sqrt |> int |> box)
    | :? int64 as i -> downcast (i |> float |> sqrt |> int64 |> box)
    | _ -> failwith "boo"

let a = sqrt_int 9
let b = sqrt_int 100L
let c = sqrt_int "foo" // boom

【讨论】:

有趣。现在我有太多选择了!一个问题:为什么在 x 上指定类型约束时需要泛型说明符 。我认为它们是等效的语法。 你确实可以省略 (试试看)。注意潜在的性能差异;方法重载在编译时确定哪个版本,而此版本在运行时进行类型检查。 (可能是“sqrt”压倒了这些考虑因素,但我没有衡量。) 当然另一个区别是这个版本为非整数编译(并在运行时抛出),而方法重载版本将为非整数编译时错误。【参考方案5】:

不要拿走已经提供的正确答案,但实际上您可以在模式匹配中使用类型约束。语法是:

| :? type ->

或者如果你想结合类型检查和强制转换:

| :? type as foo ->

【讨论】:

这就是我最初认为我可以做到的。不幸的是,它给出了“运行时强制”错误(错误 FS0008)。与 mausch 帖子中链接中提供的 retype 功能一起,如果我理解正确的话,它应该可以替代 inline 关键字。 可以通过使用match box variable with将类型匹配的变量装箱来避免运行时强制

以上是关于具有泛型参数类型的函数的主要内容,如果未能解决你的问题,请参考以下文章

“T”必须是具有公共的无参数构造函数的非抽象类型,才能用作泛型类型或方法

请教一个unity有关于泛型参数的问题

将具有类型参数的类作为Java中泛型方法的类型参数传递

泛型约束

Kotlin - 推断两个泛型参数之一的类型

Kotlin泛型 ① ( 泛型类 | 泛型参数 | 泛型函数 | 多泛型参数 | 泛型类型约束 )