在Scala中,为什么不能实现这样的琐碎的泛型函数?

In Scala, why can't I implement a trivial generic function like this?

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

1
2
3
4
5
6
double("A")
>"AA"
double(1)
> 2
double(0.2)
> 0.4

所以我这样写这个函数:

1
def double[T](x:T):T = { x+x }

但是当我在REPL中运行它时,scala对此表示抱怨:

1
2
3
4
5
6
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 }
                                  ^

我认为结构类型可能是实现鸭子类型的一种方法,我尝试了类似的方法,但是它也不起作用:

1
2
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 }

有人对此有想法吗?谢谢!

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

1
double x = x + x

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


并非每个类型T都有+方法,因此该方法不起作用。奇怪的错误消息来自编译器,将第一个x视为String,因为每种类型都有一个toString方法,这是它可以将通用T视为具有。但是随后T被传递给+而不是String,并且它需要第二次隐式转换才能使它起作用-即使我们这样做了,它也会返回String而不是T

问题是,我们需要一种方法来提供证据证明T具有+操作。据我所知,标准库中没有任何东西可以做到这一点,但是我们可以创建一个类型类,以提供类型可以被"加倍"的证据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
    }
}

现在我们可以定义double方法,该方法需要类型为CanDouble类型的证据。

1
2
3
4
5
6
7
8
9
10
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

理想情况下,您会将所有类型类实例(例如StringDoublenumericDouble)放置在伴随对象CanDouble中。

我认为结构类型在这里根本无法工作,因为不允许您在结构细化之外定义的结构细化中使用抽象类型参数(类型参数T)。从SLS:

Within a method declaration in a structural refinement, the type of any value parameter may only refer to type parameters or abstract types that are contained inside the refinement. That is, it must refer either to a type parameter of the method itself, or to a type definition within the refinement. This restriction does not apply to the method's result type.

通常应避免使用结构类型,因为它们非常慢。在这种情况下,应首选类型类。


在haskell中,您可以:

1
2
3
4
5
6
7
Prelude> let double x = x + x // (1)
Prelude> let quadruple x = double (double x) //(2)

Prelude> :t double
double :: Num a => a -> a
Prelude> :t quadruple
quadruple :: Num a => a -> a

在scala中,您必须明确指定Num

1
2
3
4
5
scala> def double[T: Numeric] (a: T) = implicitly[Numeric[T]].plus(a, a)
double: [T](a: T)(implicit evidence$1: Numeric[T])T

scala> def quadruple[T: Numeric](a: T) = double(double(a))
quadruple: [T](a: T)(implicit evidence$1: Numeric[T])T

因为haskell的类型推断更聪明。 (1)第一行确实找到了typeclass Num

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Prelude> :info Num
class Num a where
  (+) :: a -> a -> a //looks like structural types, but ...
  (*) :: a -> a -> a
  (-) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
    -- Defined in a€?GHC.Numa? //... but here is implementations found accross build - they are explicitly saying that they are instances of Num

instance Num Integer -- Defined in a€?GHC.Numa?
instance Num Int -- Defined in a€?GHC.Numa?
instance Num Float -- Defined in a€?GHC.Floata?
instance Num Double -- Defined in a€?GHC.Floata?

Scala也存在结构类型问题-您不能定义多态结构类型(不仅如此-您不能定义多态lambda)"结构细化中的参数类型不能引用该细化之外定义的抽象类型"

否则Num在Scala中将被定义为如下内容:

1
implicit class Num[T <: { def +(x:T):T }](a: T) = ... //will not work, and pretty slow by the way

查看其他答案以了解其真正定义方式(Numeric)。

在第(2)行编译器中,从double \\的应用程序推断出x(Num x)的输入类型。 Scala无法做到这一点。它与haskell的Num类似为:

1
2
3
4
5
6
7
8
9
10
11
scala> trait Num[T]{ val a: T; def + (b: Num[T]): Num[T] }
defined trait Num

scala> implicit class NumInt(val a: Int) extends Num[Int] {override def + (b: Num[Int]) = NumInt(a + b.a)}
defined class NumInt

scala> def double[T](a: Num[T]) = a + a
double: [T](a: Num[T])Num[T]

scala> double(5)
res4: Num[Int] = NumInt@424f5762

但是问题仍然存在-您必须在scala中指定输入类型(a: Num[T]),它无法推断出它们。

但是,即使在Haskell中,您也不能说:

1
2
3
4
5
6
Prelude> let double x = x +++ x

<interactive>:28:18:
    Not in scope: a€?+++a€?
    Perhaps you meant a€?++a€? (imported from Prelude)
Otherwise `Num` would be defined in Scala as something like that:

Haskell的真实鸭子输入不太容易使用:http://chrisdone.com/posts/duck-typing-in-haskell


这是何时使用类型类的完美示例。

+只是一个函数。您没有为编译器提供任何信息,例如

1
def +(t : T, t : T) : T = ...

您不能,因为您不知道T是什么。

此处将按以下方式工作。您有一个名为Doubles:

的类型构造函数。

1
2
3
trait Doubles[T]{
  def double(t : T) : T
}

现在为了方便起见,在同伴对象中,我将按以下方式重写您的double函数:

1
2
3
object Doubles{
  def double[T](t : T)(implicit doubles : Doubles[T]) =    doubles.double(t)
}

因此,我可以将T加倍,只要范围内有T的Doubles,或者您明确为我提供了T的Doubles。否则,我将无法将T加倍,则会出现编译器错误。

以下将是该类型类Doubles [T]的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
object Implicits{
    //Now, you wouldn't want to have to write this for
    //every kind of number.  Scala already provides you with a numeric
    //typeclass.  So here's a function that gives a Doubles[N]
    //whenever you ask for a Doubles[Numeric[T]], i.e. a Doubles for a    
    //member of the Numeric typeclass:

    implicit def numDoubler[N](implicit num : Numeric[N]) : Doubles[N] = new Doubles[N]{
        def double(n : N) : N = num.plus(n,n)
    }

    implicit object stringDoubler extends Doubles[String]{
        def double(t : String) : String = t + t
    }

 //So something like this is no longer needed:
 // implicit object intDoubler extends Doubles[Int]{
 //   def double(t : Int) : Int = t + t
 // }

}