Scala3 扩展基本类型和覆盖 ==
Scala3 extensions for basic types and overriding ==
正在学习 Scala3 扩展和 CanEqual 概念,但在扩展 Int 的某些功能时遇到困难。
在下面的示例中,我可以轻松地将 >= 功能添加到 Int 以将其与 RationalNumber 案例进行比较 class,但无法修改 == 的行为。 (注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);
}
...
which note flips the order of arguments 因此会调用 RationalNumber#equals
,所以实际上
1 == one
变成
one.equals(1)
通过查看 1 == BigInt(1)
的 REPL 中的 :javap -
找到了这种方法
30: invokestatic #54 // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
然后跟随 BoxesRunTime.equals
布置的足迹
正在学习 Scala3 扩展和 CanEqual 概念,但在扩展 Int 的某些功能时遇到困难。
在下面的示例中,我可以轻松地将 >= 功能添加到 Int 以将其与 RationalNumber 案例进行比较 class,但无法修改 == 的行为。 (注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);
}
...
which note flips the order of arguments 因此会调用 RationalNumber#equals
,所以实际上
1 == one
变成
one.equals(1)
通过查看 1 == BigInt(1)
:javap -
找到了这种方法
30: invokestatic #54 // Method scala/runtime/BoxesRunTime.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
然后跟随 BoxesRunTime.equals