【问题标题】:Does scala provide anything like C++ templates?scala 是否提供类似 C++ 模板的东西?
【发布时间】:2013-08-21 00:47:07
【问题描述】:

我来自 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 中这样做?例如,是因为泛型是在实例化之前编译的吗?)

【问题讨论】:

  • 虽然 Scala 作为一种大型且兼收并蓄的语言,可能会提供类似于 C++ 模板的东西,但针对您的问题公认的主流解决方案是“类型类”。正如他们所说,在罗马的时候,就像罗马人一样。

标签: c++ scala templates generics


【解决方案1】:

在scala中实现上述的惯用方式是什么?

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

C++模板程序员正确理解的方法是什么 scala中泛型的限制? (为什么我不能在 scala 中这样做?例如 是不是因为泛型是在实例化之前编译的?)

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

基本上,C++ 模板是宏,尽管远没有#define 宏那么愚蠢。事实上,C++ 模板是图灵完备的。也许将来有可能完成类似的事情,即将为 Scala 2.11 及更高版本计划的宏功能,但我们暂时忽略它。

类型参数(Scala 等效于 Java 泛型)不会改变代码的编译方式。参数化类在编译时生成其字节码,而不是在使用时。所以,当用Double 实例化Point2 时,生成字节码已经太迟了。

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

这就是问题的根源:在编译Point2 时,必须知道T 上调用的任何方法都存在于T 上。因此,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 的隐式值。

现在,请注意在 xy 上没有调用方法——相反,我们在类为 Numeric[T]num 上调用方法。 Numeric[T] 类有一个 plus 方法,它知道如何添加两个 Ts。

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

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]

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

在普通类型继承(类型上限)之后,类型类是 Scala 中最常见的类型约束形式。还有其他一些更复杂的形式,对于它们是类型类还是不同的东西——例如磁铁模式,有一些讨论。看看shapeless 的例子,了解这样的事情能走多远。

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

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],就好像它们本身具有+- 之类的方法。

作为最后一点,重要的是要意识到这会经历许多间接级别,因此可能不太适合高性能代码。

【讨论】:

  • “基本上,C++模板就是宏”Scheme宏,和#define宏几乎没有关系。
  • @pedrofurla 我将答案指向 C++ 公众,对于他们来说,“宏”通常等同于定义。
  • 注意:您使用的术语“视图边界”实际上是指“上下文边界”
  • @RégisJean-Gilles 我已经使用了 3 次,我的意思是所有树中的“视图绑定”,尽管我承认我对最后一个的使用并不常见。在 (Numeric.Implicits)[scala-lang.org/api/current/… 上,有一个隐式转换使 T(有一个 Numeric 上下文绑定)可以Ops 的形式查看。我会改写成更清楚的。
  • “事实上,C++ 模板已经完成。也许将来有可能完成类似的事情” 未来就是现在。 Scala 隐式解析也是图灵完备的,以及自 Scala 2.10 以来可用的 Scala 宏系统。此外,Scala 宏系统远没有 C++ 模板那么受限——它可以进行任意 AST 转换/代码生成,而不仅仅是就地盲符号替换。 C++ 模板仍然更接近旧的#defines,而不是 Scheme 或 Scala 宏。
【解决方案2】:

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

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>

【讨论】:

    【解决方案3】:

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

    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)
    

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

    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 函数是将在调用站点内联的代码模板。

    【讨论】:

    • 你可能有一个非常好的库,但这是一个非常糟糕的答案,因为它没有解释库是如何工作的。
    • @n.m.它之所以有效,是因为 @template 函数是将在调用站点内联的代码模板。
    • 这应该是答案的一部分。
    • 现在是。感谢您的反馈!
    • 这方面有什么更新吗?它有没有像它一样进入scala?
    【解决方案4】:

    这需要一个类型类(我称之为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
    }
    

    【讨论】:

      【解决方案5】:

      使用Numeric,可用作implicit

      import scala.math.Numeric;
      case class Point2[T](x: T, y: T)(implicit num: Numeric[T])
      

      看看Numericin the API,做你需要的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-01-25
        • 2016-08-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多