Search code examples
scalatypeclassimplicitscala-3given

How can I implement Fractional[Int] and get access to new arithmetic operations


I am trying to create a Fractional[Int] instance in Scala 3 that I want to use for finite field arithmetic.

I have a class whose instances can work as Fractional[Int] implementations:

class IntModp(val p : Int) extends Fractional[Int] {
  def inverse(a : Int) : Int = { 
    // implementation of modular inverse with fast Euclidean algorithm here
  }
  val inverses : Map[Int, Int] = Map.from(
    Range(1, p-1).map(j => (j -> inverse(j))) ++
      Range(-(p-1), -1).map(j => (j -> -inverse(-j)))
  )
  def norm(a: Int): Int = {
    val r : Int = a % p
    return r match {
      case rr : Int if (rr < -(p-1)/2) => rr + p
      case rr : Int if (rr > (p-1)/2) => rr-p
      case rr : Int => rr
    }
  }

  def compare(x: Int, y: Int): Int = Ordering.Int.compare(norm(x), norm(y))
  def div(x: Int, y: Int): Int = times(x, inverses(norm(y)))
  def fromInt(x: Int): Int = norm(x)
  def minus(x: Int, y: Int): Int = norm(x - y)
  def negate(x: Int): Int = norm(-x)
  def parseString(str: String): Option[Int] =
    IntIsIntegral.parseString(str).map(j => norm(j))
  def plus(x: Int, y: Int): Int = norm(x + y)
  def times(x: Int, y: Int): Int = norm(x * y)
  def toDouble(x: Int): Double = IntIsIntegral.toDouble(norm(x))
  def toFloat(x: Int): Float = IntIsIntegral.toFloat(norm(x))
  def toInt(x: Int): Int = IntIsIntegral.toInt(norm(x))
  def toLong(x: Int): Long = IntIsIntegral.toLong(norm(x))
}

According to my understanding of Scala 3 type classes, and of scala.math.Fractional.Implicits, I should now be able to do something like the following:

given IntMod17 : Fractional[Int] = new IntModp(17)
15 + 12 // expect to see -7, instead see 27
15 / 12 // expect to see -3, instead see 1

But instead, whenever I actually use any of the arithmetic operators I get the "normal" Int behavior. I can of course do something like

given IntMod17 : Fractional[Int] = new IntModp(17)
val fr = summon[Fractional[Int]] // fr is now this IntMod17 object
fr.plus(15, 12) // expect to see -7, and see -7
fr.div(15, 12) // expect to see -3, and see -3

Solution

  • The class Int already has methods (normal, not extension methods) +, /. You can't change their behavior.

    In Scala if for x.foo there are an extension method foo (defined with implicits or in Scala 3 with extension keyword) and a normal method foo then the latter method wins.

    An example:

    class A:
      def foo = println("normal method")
    
    implicit class AOps(a: A):
      def foo = println("extension method 1")
    
    extension (a: A)
      def foo = println("extension method 2")
    
    class AOps1(a: A):
      def foo = println("extension method 3")
    given Conversion[A, AOps1] = AOps1(_)
    
    trait MyTypeclass[X]:
      def foo = println("extension method 4")
    extension [X: MyTypeclass](a: X)
      def foo = summon[MyTypeclass[X]].foo
    given MyTypeclass[A] with {}
    
    trait MyTypeclass1[X]:
      extension (a: X)
        def foo = println("extension method 5")
    given MyTypeclass1[A] with {}
    
    val a = new A
    a.foo // normal method