【问题标题】:Scala3 extensions for basic types and overriding ==Scala3 扩展基本类型和覆盖 ==
【发布时间】:2021-09-09 18:30:21
【问题描述】:

学习 Scala3 扩展和 CanEqual 概念,但在扩展 Int 的某些功能时发现困难。

在以下示例中,我可以轻松地将 >= 功能添加到 Int 以将其与 RationalNumber 案例类进行比较,但无法修改 == 的行为。 (注1~2与RationalNumber(1,2)相同)。

这个问题似乎与基本的 AnyVal 类型以及 Scala 如何传​​递给 Java 来处理 equals 和 == 有关。

case class RationalNumber(val n: Int, val d: Int):
  def >=(that:RationalNumber) = this.num * that.den >= that.num * this.den
  //... other comparisons hidden (note not using Ordered for clarity)
  private def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b)
  val sign = if (n<0 ^ d<0) -1 else 1
  private val (an, ad) = (math.abs(n), math.abs(d))
  val num = sign * (an / gcd(an, ad))
  val den = if(an == 0) 1 else ad / gcd(an, ad)

  override def equals (that: Any): Boolean =
    that match
      case t: RationalNumber => t.den == den && t.canEqual(this) && t.num == num
      case t: Int => equals(RationalNumber(t,1))
      case _ => false

  override lazy val toString = s"$num/$den"

object RationalNumber:
  def apply (r: Int): RationalNumber = new RationalNumber(r, 1)
  import scala.language.implicitConversions
  implicit def intToRat (i: Int): RationalNumber = i ~ 1
  given CanEqual[RationalNumber, Int] = CanEqual.derived
  given CanEqual[Int, RationalNumber] = CanEqual.derived
  extension (i: Int)
    def ~(that: Int) = new RationalNumber(i, that)
    def >=(that: RationalNumber) = i ~ 1 >= that
    def equals (that: AnyVal) : Boolean =
      println("this never runs")
      that match
        case t: RationalNumber => t.den == 1 && t.num == i
        case _ => i == that
    def ==(that: RationalNumber) =
      println ("this never runs")
      i~1 == that

object Main:
  @main def run =
    import RationalNumber._
    val one = 1 ~ 1
    val a = 1 == one // never runs extension ==
    val b = one == 1
    val c = 1 >= one
    val d = one >= 1
    val ans = (a,b,c,d) // (false, true, true, true)
    println(ans)

【问题讨论】:

    标签: scala extension-methods equals scala-3 dotty


    【解决方案1】:

    仅当不存在同名的限定方法时才尝试扩展方法。因此,至少在Int 上已经定义了以下符合条件的==

    def ==(arg0: Any): Boolean
    

    它不会呼叫您的分机。如果你把名字改成=== 就可以了

    def ===(that: RationalNumber)
    

    如果需要,您可以强制使用类型归属 (1: RationalNumber) == one 进行隐式转换。 (不鼓励隐式转换)。


    尝试扩展 ScalaNumericConversions 进而扩展 ScalaNumber

    case class RationalNumber(val n: Int, val d: Int) extends ScalaNumericConversions {
      def intValue: Int = ???
      def longValue: Long = ???
      def floatValue: Float = ???
      def doubleValue: Double = ???
      def isWhole: Boolean = false
      def underlying = this
    ...
      override def equals (that: Any): Boolean = {
        that match {
          case t: RationalNumber => t.den == den && t.canEqual(this) && t.num == num
          case t: Int => equals(RationalNumber(t,1))
          case _ => false
        }
      }
    }
    

    所以现在 Scala 最终会调用 BoxesRuntime#equalsNumNum

    public static boolean equalsNumNum(java.lang.Number xn, java.lang.Number yn) {
    ...
      if ((yn instanceof ScalaNumber) && !(xn instanceof ScalaNumber))
        return yn.equals(xn);
      }
    ...
    

    哪个注释翻转了参数的顺序,因此会调用RationalNumber#equals,所以实际上

    1 == one
    

    变成

    one.equals(1)
    

    通过查看 REPL 中的 :javap - 1 == BigInt(1) 找到了这种方法

    30: invokestatic  #54  // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
    

    然后跟随BoxesRunTime.equals布置的线索

    【讨论】:

    • 通过该参数 def >= 已经存在并且不应该工作。但事实上确实如此。我正在寻找为什么 == 工作方式不同。我可以为 Int 的其他方法提供扩展定义,但不能为 == 和 != 提供扩展定义。
    • @aldudalski 在Int 类上没有采用RationalNumber&gt;= 方法。但是== 需要Any,因此它将始终匹配。
    • @LuisMiguelMejíaSuárez 感谢您的澄清我只看到 Byte、Short、Char、Int、Long、Float、Double 定义。诀窍当然是通过 AnyVal 到任何 def==(argo: Any):Boolean 隐藏的地方。我想我必须走马里奥建议的 === 路线。
    • @aldudalski 是的,这很烦人,但摆脱普遍平等会很痛苦。无论如何,自定义三等号运算符在 Scala 中非常常见,至少有五个库提供自己的大声笑。
    • 后续思路:BigInt(1) == 1 和 1 == BigInt (1)。 BigInt 没有在 Int 中明确描述,但克服了这个问题
    猜你喜欢
    • 2021-03-16
    • 2014-10-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-08
    • 2011-09-28
    • 2017-06-28
    • 1970-01-01
    相关资源
    最近更新 更多