【问题标题】:How to create a generic class in Scala如何在 Scala 中创建泛型类
【发布时间】:2022-01-16 22:32:35
【问题描述】:

我试图在 Scala 中创建一个适用于所有整数类型的通用计算器。 当我尝试以下代码时,它给了我一个错误

错误:“类型不匹配。必需:字符串,找到:T”

class Calculator[T <: Integral[T]] {
  def add[T](a: T, b: T): T = a + a
}

有人可以向我解释一下它是如何期待 String 类型的吗?

【问题讨论】:

  • 这些“整数类型”是什么?

标签: scala generics


【解决方案1】:

要回答具体问题,a + b 的类型是 String,因为 Scala 有一个默认的 +,它可以通过将两个参数转换为 String 并将它们连接起来来处理任何对象。但这是说a上没有其他方法+可以使用。

更广泛的问题是Numeric 不是所有数字类型的基类,而是定义T 类型的数字行为的typeclass。所以类定义应该是这样的:

class Calculator[T : Integral] {

这告诉编译器您不能创建Calculator[T] 的实例,除非当时编译器可以看到Integral[T] 的隐式实例。

所以有一个Integral[T] 的实例,它具有对T 进行基本数字运算的方法(plustimesnegate 等)。下一个问题是,我如何获得Integral[T] 的这个实例?答案是implicitly,只要类型可用,它就会返回类型的类型值(否则是编译时错误)。

所以表达式implicitly[Integral[T]] 将返回一个Integral[T] 的实例,用于创建Calculator 的任何类型。并且该实例知道如何plus 类型为T 的值。一旦你知道了这一点,你就可以编写你的 add 函数:

class Calculator[T : Integral] {
  def add(a: T, b: T): T = implicitly[Integral[T]].plus(a, b)
}

[还要注意add上没有类型参数,因为类型参数在类上,所以不是def add[T](...)而只是def add(...)]

【讨论】:

    【解决方案2】:

    你看到的原因

    Error: "Type mismatch. Required: String, found: T"
    

    是不是默认Scala 2在Predef中定义了如下隐式方法

    implicit final class any2stringadd[A](private val self: A) extends AnyVal {
      def +(other: String): String = String.valueOf(self) + other
    }
    

    (这不会在 Scala 3 中编译)。如果您想编写适用于所有整数类型的单个非泛型计算器类(无论这对您意味着什么 - Scala 没有将类型限制为 IntByteLong 的继承层次结构) .您将使用自己的 Integer 类型类

    class Calculator {
      def add[A: Integer](lhs: A, rhs: A): A = implicitly[Integer[A]].plus(lhs, rhs)
    }
    

    有以下定义

    trait Integer[A] {
      def plus(lhs: A, rhs: A): A
    }
    
    object Integer {
      implicit val Int: Integer[Int] = new Integer[Int] {
        override def plus(lhs: Int, rhs: Int): Int = lhs + rhs
      }
    
      // note explicit toByte cast -> byte addition can overflow! 
      // (thus result type is Int and you might want to stick 
      // to the original type in your use case)
      implicit val Byte: Integer[Byte] = new Integer[Byte] {
        override def plus(lhs: Byte, rhs: Byte): Byte = (lhs + rhs).toByte
      }
    }
    

    为了让它看起来更好一点,您可以利用扩展方法。

    class Calculator {
      import Integer._
      def add[A: Integer](lhs: A, rhs: A): A = lhs + rhs
    }
    
    object Integer {
      implicit class IntegerOps[A: Integer](lhs: A) {
        def +(rhs: A): A = implicitly[Integer[A]].plus(lhs, rhs)
      }
      // the rest of the object stays the same
    }
    

    使用 Scala 3,您可以更简洁地编写它

    class Calculator {
      def add[A: Integer](lhs: A, rhs: A): A = lhs + rhs
    }
    
    trait Integer[A]:
      extension(a: A) def +(b: A): A
    given Integer[Int] with
      extension(a: Int) def +(b: Int): Int = a + b
    given Integer[Byte] with
      extension(a: Byte) def +(b: Byte): Byte = (a + b).toByte
    

    编辑:根据@Tim 的建议,您可以使用预定义的Integral 类型类。如果您想自己实现类似的类型类,我仍然会留下我的答案作为参考。

    【讨论】:

    • 既然Integral 已经存在,为什么还要定义自己的类型类?
    • 好点@Tim :)
    猜你喜欢
    • 1970-01-01
    • 2016-06-20
    • 2023-03-31
    • 1970-01-01
    • 2018-06-12
    • 1970-01-01
    • 2022-01-21
    • 1970-01-01
    • 2022-10-15
    相关资源
    最近更新 更多