我来自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()
我发现我想写这样的东西:
case class Point2[ T ] (x:T, y:T) {
def +() Point2[T]: = x+y
def sumComponents() T: = x+y
}
或,(因为编译有问题),
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
}
同样有问题,因为我不能要求Double扩展Addable。
一般来说,我发现scala的类型系统使用了一组我不太了解的约束。
在scala中实现上述内容的惯用方法是什么?
对于C ++模板程序员来说,了解scala中泛型限制的正确方法是什么? (为什么我不能在scala中执行此操作?例如,是因为泛型是在实例化之前编译的吗?)
答案 0 :(得分:17)
在scala中实现上述内容的惯用方法是什么?
通过指定T
的适当要求,或使用类型类来提供所需的行为。我稍后会回到这里。
C ++模板程序员理解的正确方法是什么 斯卡拉中泛型的限制? (为什么我不能在scala中这样做?例如 是因为泛型是在实例化之前编译的吗?)
C ++模板在“使用”站点“编译”,并为模板的每个参数组合生成不同的代码。因此,如果您将上述类与int
和double
一起使用,则可以编译两个不同的Point2
类。
基本上,C ++模板是宏,虽然远不如#define
宏那么愚蠢。事实上,C ++模板已经完成了。也许有可能在未来完成同等的工作,为Scala 2.11及更高版本计划即将推出的宏功能,但现在让我们忽略它。
类型参数(Scala等效于Java 泛型)不会更改代码的编译方式。参数化类在编译时生成字节码,而不是在使用时生成。因此,当用Point2
实例化Double
时,生成字节码为时已晚。
这意味着参数化类生成的代码必须与可以使用实例化的所有类型兼容。
这就是问题的根源:在编译T
时,T
上必须知道任何调用Point2
的方法。因此,必须将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
或者,使用称为“上下文边界”的特殊符号
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
符号T : Numeric
可以理解为 T
,因此可以使用Numeric[T]
的隐式实例。如果可以找到(或在编译时失败),代码implicitly[X]
将返回类型X
的隐式值。
现在,请注意x
和y
上没有调用任何方法 - 而是调用类num
的{{1}}上的方法。课程Numeric[T]
有一个方法Numeric[T]
,知道如何添加两个plus
。
因为我们需要的是类型类实例,所以可以轻松添加新类型以满足类型类。可以轻松地为T
声明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]
的任何T
,也会有Numeric[T]
。
在普通类型继承(上部类型边界)之后,类型类是Scala中使用的最常见的类型约束形式。还有其他形式有点复杂,对于它们有一些讨论,无论它们是类型类还是不同的东西 - 例如磁体模式。请查看shapeless,了解一个人可以采取多远措施的例子。
另一种类型的约束,曾经非常常见但现在被更加谨慎地使用 view bounds 。我不会详细介绍(实际上,搜索上下文边界和查看边界以便从我自己那里找到一个很长的答案),但它们可以用来使类型类在使用时更具可读性。例如:
Numeric[Point2[T]]
导入的定义包含隐式转换,这些转换可以使用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
类型的值,而T
就像Numeric[T]
或{+
这样的方法一样1}}。
作为最后一点,重要的是要意识到这会经历多个间接层,因此可能不太适合高性能代码。
答案 1 :(得分: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>
答案 2 :(得分:2)
使用Numeric
,implicit
。
import scala.math.Numeric;
case class Point2[T](x: T, y: T)(implicit num: Numeric[T])
查看Numeric
in the API,做你需要的。
答案 3 :(得分:2)
这需要一个类型类(我正在调用Addition
)和一个隐式转换(我通过一个名为Op
的隐式类来定义)。在实践中,您会针对此特定情况使用Numeric
类型,但出于说明目的,您可以自定义自己的类型:
trait Addition[T] {
def add(a: T, b: T): T
implicit class Op(a: T) {
def +(b: T) = add(a, b)
}
}
implicit object IntAddition extends Addition[Int] {
def add(a: Int, b: Int) = a + b
}
implicit object DoubleAddition extends Addition[Double] {
def add(a: Double, b: Double) = a + b
}
case class Point2[T](x: T, y: T)(implicit addition: Addition[T]) {
import addition.Op
def +(p: Point2[T]): Point2[T] = Point2(x + p.x, y + p.y)
def sumComponents(): T = x + y
}
答案 4 :(得分: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)
请注意,您甚至可以加上两个具有不同组件类型的Point2
。
println(Point2(1.5, 0.3) + Point2(100, 200)) // Output: Point2(101.5,200.3)
甚至嵌套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)))
它的工作原理是因为@template
函数是将在调用站点内联的代码模板。