Scala3 对基本类型和覆盖的扩展 ==

标签 scala extension-methods equals scala-3 dotty

学习 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)

最佳答案

仅当不存在同名的限定方法时才尝试扩展方法。因此,至少以下排位赛 ==已在 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)
通过查看 :javap - 找到了这种方法在 REPL 中为 1 == BigInt(1)
30: invokestatic  #54  // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
然后跟随 BoxesRunTime.equals 布置的路径

关于Scala3 对基本类型和覆盖的扩展 ==,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68146630/

相关文章:

c# - ASP.NET MVC : 'Extend' Html. TextBox 有条件地具有 CSS 值?

linq - IQueryable(非通用): missing Count and Skip ?与IQueryable <T>一起使用

C# Assert.AreNotEqual 与等于

scala 的 JavaScript 源代码生成库

string - Scala 在空格上分割字符串,不包括某些部分

scala - 如何从 Scala 中的 DataFrame 在 Spark 中创建分布式稀疏矩阵

scala - 如何衡量scala烫金程序的运行时间?

C# - 使字符串以不同的方式打印到控制台,但以其原始形式在其他地方使用

java - 如何拥有一个 "inconsistent with equals"的 TreeSet

java - 重写 hashCode() 不起作用