如何在 scala 中定义一个接受所有“可计算”数据类型的通用函数?

Mic*_* W. 3 generics scala class function

我已经遇到过这个问题好几次了:

我有一个计算某些东西的函数,可以说

def square(n: Int): Int = n * n
Run Code Online (Sandbox Code Playgroud)

(非常简单的例子,但这就可以了)

然后我对另一种数据类型有相同的“算法”,可以说很长:

def square(n: Long): Long = n * n
Run Code Online (Sandbox Code Playgroud)

然后是 BigInt、Short、Byte 等。

如果我的算法比这个例子更复杂、更长,我就会有很多代码重复。

我想要的是一个通用定义,例如:

def square[T :> AnyVal](n: T): T = n * n
Run Code Online (Sandbox Code Playgroud)

但这不起作用,因为在 hirachy 类中,在 AnyVal 与 Int、Long 和 Float 之下还有 Boolean 和 Unit。对于布尔值和单位,术语 n * n 没有意义,我得到一个编译器错误(正确)。

我只希望我的函数适用于“可计算”数据类型,如 Int、Long、Float...,它们具有所有常见的数学运算符,如 +、*、/、< 等,然后用此制定我的算法或计算一次性为所有操作员提供服务。

当然,我可以匹配函数输入变量 n,然后以不同的方式处理每种情况,但我也会像之前一样通过重载重复所有代码。

我尝试创建自己的特征“Computables”,然后扩展到其他类 Int、Long 等,但编译器抱怨“...无法扩展最终类 Int”

这可能吗?我错过了什么吗?

Gur*_*ron 7

您可以使用该Numeric特征:

def square[T](n: T)(using numeric: Numeric[T]): T = numeric.times(n,n)
Run Code Online (Sandbox Code Playgroud)

或者

def square[T](n: T)(using numeric: Numeric[T]): T = {
  import numeric._
  n * n
}
Run Code Online (Sandbox Code Playgroud)

演示@scastie

  • 从技术上讲,您不应该同时使用“[T: Numeric]”和“using Numeric[T]” - 您正在以这种方式创建不明确的隐式/给定。这个例子之所以有效,是因为您手动调用了“numeric”,但隐式参数在这里传递了两次。 (4认同)

Dim*_*ima 7

我想,你正在寻找一个类型类

Numeric另一个答案中提到的是描述所有数字类型的类型类的示例。但你可以概括它来描述任何类型的行为。

查看我上面提到的链接,它有详细信息和示例,但从总体上讲,其想法是这样的:

def someFunction[T : SomeType](t: T)相当于def someFunction[T](t: T)(implicit ev: SomeType[T])

这意味着像

val foo: Foo = ???
someFunction(foo)
Run Code Online (Sandbox Code Playgroud)

当且仅当您在范围内某处有类型的隐式实例时才会编译SomeType[Foo]

因此,这解决了问题的第一部分:我们Numeric为所有“数字”类型定义了实例,但没有为字符串或布尔值定义实例,因此通过这种方式,您可以将可以发送到函数的类型限制为特定的“类”类型的”(即“类型类”)。

类型类的另一个目的是表达它所包含的类型的常见行为。您可以“召唤”类型类的隐式“证据”来访问它:

def someFunction[T : SomeType](t: T) = implicitly[SomeType[T]].doStuff(t)
Run Code Online (Sandbox Code Playgroud)

在你的情况下,使用square

def square[T : Numeric](t: T) = implicitly[Numeric[T]].times(t, t)
Run Code Online (Sandbox Code Playgroud)