scala是否提供类似C++模板的东西?

use*_*956 7 c++ generics templates scala

我来自C++并试图围绕scala的类型系统.

考虑以下C++模板类:

template<class T>
class Point2
{
  Point2( T x, T y ) :
     x(x),
     y(y)
  {}

  T x;
  T y;
  Point2<T> operator+( Point<T> const& other ) const
  {
     return Point<T>(x+other.x, y+other.y);
  }
  T sumComponents() const { return x+y; }
}

Point2<Double> p0(12.3, 45.6) 
Point2<Double> p1(12.3, 45.6) 
Point2<Double> p = p1+p2
Double d = p1.sumComponents()
Run Code Online (Sandbox Code Playgroud)

我发现我想写这样的东西:

case class Point2[ T ] (x:T, y:T) {
   def +() Point2[T]: = x+y
   def sumComponents() T: = x+y
}
Run Code Online (Sandbox Code Playgroud)

或者,(因为编译有问题),

trait Addable[T] {   // Require T supports the + operatory
   def +( that:T ):T
}
case class Point2[ T<:Addable[T] ] (x:T, y:T) {
   def +() Point2[T]: = x+y
   def sumComponents() T: = x+y
}
Run Code Online (Sandbox Code Playgroud)

这同样有问题,因为我不能要求Double扩展Addable.

一般来说,我发现scala的类型系统使用了一组我不太了解的约束.

在scala中实现上述内容的惯用方法是什么?

那么C++模板程序员理解scala中泛型的限制的正确方法是什么?(为什么我不能在scala中执行此操作?例如,是因为泛型是在实例化之前编译的吗?)

Dan*_*ral 17

在scala中实现上述内容的惯用方法是什么?

通过指定适当的要求T,或使用类型类来提供所需的行为.我稍后再回过头来看看.

那么C++模板程序员理解scala中泛型的限制的正确方法是什么?(为什么我不能在scala中执行此操作?例如,是因为泛型是在实例化之前编译的吗?)

C++模板在使用站点"编译",并为模板的每个参数组合生成不同的代码.因此,如果您使用上面的类intdouble,则会Point2编译两个不同的类.

基本上,C++模板是宏,虽然远没有#define宏那么愚蠢.事实上,C++模板已经完成了.也许有可能在未来完成同等的工作,即将为Scala 2.11及更高版本计划即将推出的宏功能,但现在让我们忽略它.

类型参数(Scala相当于Java 泛型)不会更改代码的编译方式.参数化类在编译时生成字节码,而不是在使用时生成.因此,在实例化一个Point2with时Double,生成字节码为时已晚.

这意味着参数化类生成的代码必须与可以实例化的类的所有类型兼容.

这就是问题的根源:T必须知道任何被调用的方法在编译TPoint2都存在.因此,T必须将其定义为具有定义此类方法的特征或类的上边界,如您在示例中所示.

当然,正如你正确指出的那样,这并不总是可能的,而且这就是类型类的用武之地.类型类是一组定义了一组行为的类型.在Scala中实现的类型类被定义为其实例定义其他类的行为的类.

在您给出的示例中,如果您还需要小数除法,则可以使用Numeric类型类或Fractional类型类.类型类使用的一个简单示例是:

scala> import scala.math.Numeric
import scala.math.Numeric

scala> def sum[T](x: T, y: T)(implicit num: Numeric[T]): T = num.plus(x, y)
sum: [T](x: T, y: T)(implicit num: scala.math.Numeric[T])T
Run Code Online (Sandbox Code Playgroud)

或者,使用称为"上下文边界"的特殊符号,

scala> def sum[T : Numeric](x: T, y: T): T = implicitly[Numeric[T]].plus(x, y)
sum: [T](x: T, y: T)(implicit evidence$1: scala.math.Numeric[T])T
Run Code Online (Sandbox Code Playgroud)

符号T : Numeric可以被读取为TNumeric[T]可用的隐式实例.如果可以找到(或在编译时失败),代码implicitly[X]将返回类型的隐式值X.

现在,请注意没有一种方法是如何被调用上xy-相反,我们呼吁的方法num,它的类Numeric[T].该类Numeric[T]有一个plus知道如何添加两个Ts的方法.

因为我们需要的是类型类实例,所以可以轻松添加新类型以满足类型类.可以很容易地声明一个Numeric类型类Point2(假设它的所有方法都可以实现):

class Point2Numeric[T](implicit num: Numeric[T]) extends Numeric[Point2[T]] {
  def plus(x: Point2[T], y: Point2[T]): Point2[T] = x + y
  // etc
}
implicit def ToPoint2Numeric[T : Numeric] = new Point2Numeric[T]
Run Code Online (Sandbox Code Playgroud)

有了这个,那么对于任何T有的Numeric[T],也会有Numeric[Point2[T]].

在普通类型继承(上层类型边界)之后,类型类是Scala中使用的最常见的类型约束形式.还有其他形式有点复杂,对于它们有一些讨论,无论它们是类型类还是不同的东西 - 例如磁体模式.看看没有形状的一个例子,说明一个人可以走多远.

另一种类型约束过去非常常见,但现在被更加谨慎地使用是视图边界.我不会详细介绍(实际上,搜索上下文边界和查看边界以便从我自己那里找到一个很长的答案),但它们可以用来使类型类在使用时更具可读性.例如:

scala> import scala.math.Numeric.Implicits._
import scala.math.Numeric.Implicits._

scala> def sum[T : Numeric](x: T, y: T): T = x + y
sum: [T](x: T, y: T)(implicit evidence$1: scala.math.Numeric[T])T
Run Code Online (Sandbox Code Playgroud)

导入的定义包含隐式转换,使其能够使用类型的值T对于这有一个Numeric[T],好像他们,自己,有一个像的方法+-.

最后要注意的是,重要的是要意识到这会经历多个间接层,因此可能不太适合高性能代码.


Eas*_*sun 6

简单地说,你可以这样做:

scala> :paste
// Entering paste mode (ctrl-D to finish)

import math.Numeric
import math.Numeric.Implicits._

case class Point2[A: Numeric](x: A, y: A) {
  def + (other: Point2[A]): Point2[A] =
    Point2(this.x + other.x, this.y + other.y)

  def sumComponents: A = x + y
}

// Exiting paste mode, now interpreting.

import math.Numeric
import math.Numeric.Implicits._
defined class Point2

scala> val p1 = Point2(1, 2)
p1: Point2[Int] = Point2(1,2)

scala> val p2 = Point2(3, 4)
p2: Point2[Int] = Point2(3,4)

scala> p1 + p2
res2: Point2[Int] = Point2(4,6)

scala> val p3 = Point2(1.2, 3.4)
p3: Point2[Double] = Point2(1.2,3.4)

scala> val p4 = Point2(1.6, 6.4)
p4: Point2[Double] = Point2(1.6,6.4)

scala> p3 + p4
res3: Point2[Double] = Point2(2.8,9.8)

scala>
Run Code Online (Sandbox Code Playgroud)


Yan*_* Bo 2

我创建了一个库template.scala。您可以使用该库创建 C++ 风格的模板,避免复杂的implicit操作。

import com.thoughtworks.template
case class Point2[T](x:T, y:T) {
   @template def +(rhs: Point2[_]) = Point2(x + rhs.x, y + rhs.y)
   @template def sumComponents() = x + y
}

println(Point2(1, 3).sumComponents()) // Output: 4
println(Point2(1, 3) + Point2(100, 200)) // Output: Point2(101,203)
Run Code Online (Sandbox Code Playgroud)

请注意,您甚至可以加上两个Point2具有不同组件类型的 s。

println(Point2(1.5, 0.3) + Point2(100, 200)) // Output: Point2(101.5,200.3)
Run Code Online (Sandbox Code Playgroud)

甚至嵌套Point2

// Output: Point2(Point2(10.1,20.2),Point2(101.0,202.0))
println(Point2(Point2(0.1, 0.2), Point2(1.0, 2.0)) + Point2(Point2(10, 20), Point2(100, 200)))
Run Code Online (Sandbox Code Playgroud)

它之所以有效,是因为@template函数是将在调用站点内联的代码模板。