在Scala中,为什么我不能实现像这样的普通泛型函数?

Han*_*Sun 3 generics functional-programming scala type-inference duck-typing

我想要一个名为"double"的泛型函数,其行为与此类似,可以应用于任何类型的def +(x:T):T方法:

double("A")
> "AA"
double(1)
> 2
double(0.2)
> 0.4
Run Code Online (Sandbox Code Playgroud)

所以我写这个函数是这样的:

def double[T](x:T):T = { x+x }
Run Code Online (Sandbox Code Playgroud)

但是当我在REPL中运行它时,scala会对此进行评估:

scala> def double[T](x:T):T = { x+x }
<console>:7: error: type mismatch;
 found   : T
 required: String
       def double[T](x:T):T = { x+x }
                                  ^
Run Code Online (Sandbox Code Playgroud)

我认为结构类型可能是实现duck typing的一种方法,我尝试过这样的东西,但它也不起作用:

def double[T <: { def +(x:T):T }](x:T):T = { x + x }
def double[T <: { def +[U<:T](x:U):U}](x:T) = { x + x }
Run Code Online (Sandbox Code Playgroud)

有没有人有这个想法?谢谢!


我发现在Haskell中,类似的函数可以像这样写:

double x = x + x
Run Code Online (Sandbox Code Playgroud)

只是想知道为什么我不能在Scala中这样做...

Mic*_*jac 11

并非每种类型T都有+方法,因此无法正常工作.奇怪的错误消息来自编译器处理第一xString,因为每个类型都有一个toString方法,那就是它可以看到一个通用的唯一途径T具有+.但是后来T传递给了+,而不是String,它需要第二次隐式转换才能使它工作 - 即使我们这样做,也会返回String而不是T.

问题是我们需要一种方法来提供T+操作的证据.据我所知,标准库中没有任何东西可以完成此操作,但我们可以创建一个类型类,它可以提供类型可以"加倍"的证据.

trait CanDouble[A] {
    def double(a: A): A
}

// Create some instances for types we know, like String, or numeric types
implicit val StringDouble: CanDouble[String] = new CanDouble[String] {
    def double(a: String): String = a + a
}

// Uses the Numeric type class to create a CanDouble for all Numeric types
implicit def numericDouble[A: Numeric]: CanDouble[A] = {
    new CanDouble[A] {
         def double(a: A): A = implicitly[Numeric[A]].plus(a, a)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以定义double需要类型类证据的方法CanDouble.

def double[A: CanDouble](a: A): A = implicitly[CanDouble[A]].double(a)

scala> double(1)
res4: Int = 2

scala> double(0.4)
res5: Double = 0.8

scala> double("a")
res6: String = aa
Run Code Online (Sandbox Code Playgroud)

理想情况下,你会把所有的种类等的类实例StringDoublenumericDouble同伴对象中CanDouble.


我不认为结构类型在这里可以工作,因为不允许在精化(类型参数T)之外定义的结构细化中使用抽象类型参数.来自SLS:

在结构细化中的方法声明中,任何值参数的类型只能引用细化内包含的类型参数或抽象类型.也就是说,它必须引用方法本身的类型参数,或者引用细化中的类型定义.此限制不适用于方法的结果类型.

无论如何,通常应该避免结构类型,因为它们非常慢.在此方案中,应首选类型类.