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

时间:2021-06-29 21:04:29

I want a generic function called "double", which behaves like this and could be applied to any type with def +(x:T):T method:

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

double("A")
> "AA"
double(1)
> 2
double(0.2)
> 0.4

So I write this function like this:

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

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

But when I run it in REPL, scala compains about it:

但是当我在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 }
                                  ^

I think structural type may be an approach to implement duck typing, and I tried something like this, but it doesn't work either:

我认为结构类型可能是实现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 }

Does anyone have ideas about this? Thanks!

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


I found in Haskell, the similar function can be written like this:

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

double x = x + x

just wondering why I can't do this in Scala...

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

3 个解决方案

#1


Not every type T has a + method, so that can't work. The strange error message comes from the compiler treating the first x as a String, because every type has a toString method, and that's the only way it can see a generic T as having +. But then T is being passed to +, instead of String, and it would require a second implicit conversion to allow this to work--and even if we did that, it would return String instead of T.

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

The problem is that we need a way to provide evidence that T has a + operation. There isn't anything in the standard library that does exactly this, to my knowledge, but we can create a type class that would provide the evidence that a type can be "doubled".

问题是我们需要一种方法来提供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)
    }
}

Now we can define the double method that requires a evidence of the type class CanDouble.

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

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

Ideally, you would put all of the type class instances like StringDouble and numericDouble within the companion object CanDouble.

理想情况下,您可以将所有类型类实例(如StringDouble和numericDouble)放在伴随对象CanDouble中。


I don't think a structural type can work at all here, because you are not allowed to use an abstract type parameter within the structural refinement that is defined outside of the refinement (the type parameter T). From the SLS:

我不认为结构类型在这里可以工作,因为不允许在精化之外定义的结构细化中使用抽象类型参数(类型参数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.

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

Structural types should typically be avoided, anyway, as they are quite slow. Type classes should be preferred in this scenario.

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

#2


In haskell you can:

在haskell你可以:

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

In scala you have to specify Num explicitly

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

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

Because haskell's type inferrence is smarter. (1)st line did find typeclass Num:

因为haskell的类型推理更聪明。 (1)st line确实找到了类型数Num:

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 ‘GHC.Num’ //... but here is implementations found accross build - they are explicitly saying that they are instances of Num

instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

Also Scala has problems with structural types - you can't define polymorphic structural type (not only this - you ca not define polymorphic lambdas) "Parameter type in structural refinement may not refer to an abstract type defined outside that refinement"

此外,Scala在结构类型方面存在问题 - 您无法定义多态结构类型(不仅如此 - 您无法定义多态lambda)“结构细化中的参数类型可能不会引用在该细化之外定义的抽象类型”

Otherwise Num would be defined in Scala as something like that:

否则Num将在Scala中定义为:

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

See other answers to find out how it's really defined (Numeric).

查看其他答案,了解它是如何定义的(数字)。

In the (2)nd line compiler inferred input type for x (Num x) from double's application. Scala just can't do that. Its analog to the haskell's Num would be:

在(2)nd行编译器中,从double的应用程序中推断出x(Num x)的输入类型。斯卡拉不能这样做。它类似于haskell的Num将是:

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

But the problem is still same - you have to specify input types (a: Num[T]) in scala, it cannot infer them.

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

However, even in Haskell you can't let's say:

然而,即使在Haskell你也不能说:

Prelude> let double x = x +++ x

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

And Haskell's real duck typing is not so easy to use: http://chrisdone.com/posts/duck-typing-in-haskell

而Haskell真正的鸭子打字并不是那么容易使用:http://chrisdone.com/posts/duck-typing-in-haskell

#3


This is a perfect example of when to use typeclasses.

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

+ is just a function. You've given the compiler no information such as

+只是一个功能。你没有给编译器提供任何信息

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

And you couldn't, because you don't have any idea what a T is.

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

Here it would work as follows. You have a type constructor, called Doubles:

这里的工作原理如下。你有一个类型构造函数,称为双打:

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

Now in a companion object, just for convenience, I'm going to rewrite your double function as follows:

现在在一个伴侣对象中,为了方便起见,我将重写你的双重函数,如下所示:

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

So this says, I can double a T, as long as there's a Doubles for T in scope, or you explicitly provide me with a Doubles for T. Otherwise, I won't be able to double a T, and you'll get a compiler error.

所以这说,我可以加倍T,只要在范围内有一个双打T,或者你明确地为我提供了一个双打。否则,我将无法加倍T,你会得到编译器错误。

The following are going to be instances of that typeclass Doubles[T]:

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

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
 // }

}

#1


Not every type T has a + method, so that can't work. The strange error message comes from the compiler treating the first x as a String, because every type has a toString method, and that's the only way it can see a generic T as having +. But then T is being passed to +, instead of String, and it would require a second implicit conversion to allow this to work--and even if we did that, it would return String instead of T.

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

The problem is that we need a way to provide evidence that T has a + operation. There isn't anything in the standard library that does exactly this, to my knowledge, but we can create a type class that would provide the evidence that a type can be "doubled".

问题是我们需要一种方法来提供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)
    }
}

Now we can define the double method that requires a evidence of the type class CanDouble.

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

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

Ideally, you would put all of the type class instances like StringDouble and numericDouble within the companion object CanDouble.

理想情况下,您可以将所有类型类实例(如StringDouble和numericDouble)放在伴随对象CanDouble中。


I don't think a structural type can work at all here, because you are not allowed to use an abstract type parameter within the structural refinement that is defined outside of the refinement (the type parameter T). From the SLS:

我不认为结构类型在这里可以工作,因为不允许在精化之外定义的结构细化中使用抽象类型参数(类型参数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.

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

Structural types should typically be avoided, anyway, as they are quite slow. Type classes should be preferred in this scenario.

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

#2


In haskell you can:

在haskell你可以:

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

In scala you have to specify Num explicitly

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

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

Because haskell's type inferrence is smarter. (1)st line did find typeclass Num:

因为haskell的类型推理更聪明。 (1)st line确实找到了类型数Num:

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 ‘GHC.Num’ //... but here is implementations found accross build - they are explicitly saying that they are instances of Num

instance Num Integer -- Defined in ‘GHC.Num’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Double -- Defined in ‘GHC.Float’

Also Scala has problems with structural types - you can't define polymorphic structural type (not only this - you ca not define polymorphic lambdas) "Parameter type in structural refinement may not refer to an abstract type defined outside that refinement"

此外,Scala在结构类型方面存在问题 - 您无法定义多态结构类型(不仅如此 - 您无法定义多态lambda)“结构细化中的参数类型可能不会引用在该细化之外定义的抽象类型”

Otherwise Num would be defined in Scala as something like that:

否则Num将在Scala中定义为:

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

See other answers to find out how it's really defined (Numeric).

查看其他答案,了解它是如何定义的(数字)。

In the (2)nd line compiler inferred input type for x (Num x) from double's application. Scala just can't do that. Its analog to the haskell's Num would be:

在(2)nd行编译器中,从double的应用程序中推断出x(Num x)的输入类型。斯卡拉不能这样做。它类似于haskell的Num将是:

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

But the problem is still same - you have to specify input types (a: Num[T]) in scala, it cannot infer them.

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

However, even in Haskell you can't let's say:

然而,即使在Haskell你也不能说:

Prelude> let double x = x +++ x

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

And Haskell's real duck typing is not so easy to use: http://chrisdone.com/posts/duck-typing-in-haskell

而Haskell真正的鸭子打字并不是那么容易使用:http://chrisdone.com/posts/duck-typing-in-haskell

#3


This is a perfect example of when to use typeclasses.

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

+ is just a function. You've given the compiler no information such as

+只是一个功能。你没有给编译器提供任何信息

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

And you couldn't, because you don't have any idea what a T is.

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

Here it would work as follows. You have a type constructor, called Doubles:

这里的工作原理如下。你有一个类型构造函数,称为双打:

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

Now in a companion object, just for convenience, I'm going to rewrite your double function as follows:

现在在一个伴侣对象中,为了方便起见,我将重写你的双重函数,如下所示:

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

So this says, I can double a T, as long as there's a Doubles for T in scope, or you explicitly provide me with a Doubles for T. Otherwise, I won't be able to double a T, and you'll get a compiler error.

所以这说,我可以加倍T,只要在范围内有一个双打T,或者你明确地为我提供了一个双打。否则,我将无法加倍T,你会得到编译器错误。

The following are going to be instances of that typeclass Doubles[T]:

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

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
 // }

}