0

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
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • If I understand correctly, you need an implicit conversion. But for that you need a type. IntMod17 is a value. How do you expect that it could work ? – volia17 Feb 23 '23 at 08:15
  • @volia17 I guess there is misunderstanding. OP doesn't need an implicit conversion. OP is declaring the type `Int` an instance of the type class `Fractional`. `IntMod17` is the corresponding implicit. – Dmytro Mitin Feb 23 '23 at 10:10
  • 2
    @dmytro-mitin I have understood that the goal was something like that : https://scastie.scala-lang.org/uy4n7JGVSJude6XxYkJtPA – volia17 Feb 23 '23 at 11:06
  • @volia17 Nice approach (although not using type classes `Fractional` or `Numeric`). – Dmytro Mitin Feb 23 '23 at 11:10
  • @volia17 Ooooooh, very nice approach. And renaming `Int` to `IntP` (with the opaque declaration) makes it much easier to emphasize when arithmetic is in the finite field and when it isn't. Thank you very much!! – Mikael Vejdemo-Johansson Feb 23 '23 at 15:19

1 Answers1

2

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
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Thank you for the explanation - it has been somewhat maddening to not find this spelled out anywhere. Between your observation and the `opaque` approach by @volia17, I have a much more promising avenue to pursue. – Mikael Vejdemo-Johansson Feb 23 '23 at 15:20