Converting implicit class to extension - issues

Consider following code using implicit class. I would like to use extension instead, but I run into several issues:

type IXY = (Int, Int)

implicit class IXYOps(private val xy: IXY) extends AnyVal {
  inline def x: Int = xy._1
  inline def y: Int = xy._2
  def +(that: IXY): IXY = (xy._1 + that._1, xy._2 + that._2)
  def -(that: IXY): IXY = (xy._1 - that._1, xy._2 - that._2)
  def *(that: IXY): IXY = (xy._1 * that._1, xy._2 * that._2)
  def /(that: IXY): IXY = (xy._1 / that._1, xy._2 / that._2)
  def *(x: Int): IXY = (xy._1 * x, xy._2 * x)
  def /(x: Int): IXY = (xy._1 / x, xy._2 / x)
  def unary_- : IXY = (-xy._1, -xy._2)
  def min(that: IXY): IXY = (x min that.x, y min that.y)
  def max(that: IXY): IXY = (x max that.x, y max that.y)

  def manhattanSize: Int = math.abs(x) + math.abs(y)
  def manhattanDistance(that: IXY): Int = (this - that).manhattanSize

  def dot(that: IXY): Int = x * that.x + y * that.y
  def size2: Double = xy.dot(xy)
  def size: Double = math.sqrt(xy.dot(xy))
  def dist(that: IXY): Double = (this - that).size

  inline def map[X](f: Int => X): (X, X) = (f(xy._1), f(xy._2))
  def abs: IXY = map(math.abs)
  def toSeq: Seq[Int] = Seq(xy._1, xy._2)
}

implicit class DoubleTupleOps(private val xy: (Double, Double)) extends AnyVal {

  inline def x: Double = xy._1
  inline def y: Double = xy._2
  def +(that: (Double, Double)): (Double, Double) =
    (xy._1 + that._1, xy._2 + that._2)
  def -(that: (Double, Double)): (Double, Double) =
    (xy._1 - that._1, xy._2 - that._2)
  def *(that: (Double, Double)): (Double, Double) =
    (xy._1 * that._1, xy._2 * that._2)
  def /(that: (Double, Double)): (Double, Double) =
    (xy._1 / that._1, xy._2 / that._2)
  def *(x: Double): (Double, Double) = (xy._1 * x, xy._2 * x)
  def /(x: Double): (Double, Double) = (xy._1 / x, xy._2 / x)
  def min(that: (Double, Double)): (Double, Double) =
    (x min that.x, y min that.y)
  def max(that: (Double, Double)): (Double, Double) =
    (x max that.x, y max that.y)
  def size2: Double = xy._1 * xy._1 + xy._2 * xy._2

  inline def map[X](f: Double => X): (X, X) = (f(xy._1), f(xy._2))
  def abs: (Double, Double) = map(math.abs)
  def toSeq: Seq[Double] = Seq(xy._1, xy._2)

}

val doubles = (0.0, 1.0) - (1.0, 0.0)
val ints = (0, 1).manhattanDistance((1, 0))

When I convert IXYOps to extension, I get error:

value manhattanSize is not a member of (IXY) => IXY.
An extension method was tried, but could not be fully constructed:

    manhattanSize(
      {
        def $anonfun(that: IXY): IXY = this.-(that)(that)
        closure($anonfun)
      }
    )

See Scastie - An interactive playground for Scala.

When I remove the size and manhattanSize methods and try to convert both IXYOps and DoubleTupleOps, i get errors like this:

Double definition:
def +(xy: IXY)(that: IXY): IXY in object XX at line 8 and
def +(xy: (Double, Double))(that: (Double, Double)): (Double, Double) in object XX at line 33
have the same type after erasure.

See Scastie - An interactive playground for Scala.

How can I work around this?

For the first issue, change this to xy

Thanks. That was a silly mistake. The second issue seems to be harder to work around, though. I hope someone will suggest something.

1 Like

I got a bit closer. When I define each extension in a separate object and import both, it works Scastie - An interactive playground for Scala.

import IXYOps.*
import DoubleTupleOps.*

A natural way to proceed would be:

object TupleOps {
  export IXYOps.*
  export DoubleTupleOps.*
}

import TupleOps.*

This does not work, though (Scastie - An interactive playground for Scala.):

Double definition:
final def toSeq(xy: IXY): Seq[Int] in object TupleOps at line 62 and
final def toSeq(xy: (Double, Double)): Seq[Double] in object TupleOps at line 63
have the same type after erasure.

There is a workaroung using @targetName, but man, it does look silly - Scastie - An interactive playground for Scala.

    @targetName("double_map") inline def map[X](f: Double => X): (X, X) = (f(xy._1), f(xy._2))
    @targetName("double_abs") def abs: (Double, Double) = map(math.abs)
    @targetName("double_toSeq") def toSeq: Seq[Double] = Seq(xy._1, xy._2)

I ran into this same issue (same signature after type erasure) few months ago. I remember asking on Discord, there really wasn’t a good solution. It’s a gotcha with extension methods I think.