18446744073709551615
18446744073709551615

Reputation: 16832

scala 3 opaque type: cannot call methods defined in extension

I have this code mostly borrowed from the Scala 3 book:

object Logarithms:
//vvvvvv this is the important difference!
  opaque type Logarithm = Double

  object Logarithm:
    def apply(d: Double): Logarithm = math.log(d)

  extension (x: Logarithm)
    def toDouble: Double = math.exp(x)
    def + (y: Logarithm): Logarithm = x + math.log1p(math.exp(y-x))
    def * (y: Logarithm): Logarithm = x + y
    //  vvvvvvvvv these two do not work!
    def toString: String = x.asInstanceOf[Logarithm].toDouble.toString
    def toString2: String = x.asInstanceOf[Logarithm].toDouble.toString
    //def toString: String = x.toDouble.toString // does not work either

The problem is that I cannot make toString work, it does not convert the value from the internal representation:

$ ~/Downloads/scala3-3.1.3/bin/scala
Welcome to Scala 3.1.3 (14.0.1, Java OpenJDK 64-Bit Server VM).

scala> import Logarithms.*
                                                                                                                           
scala> Logarithm(4)
val res0: Logarithms.Logarithm = 1.3862943611198906   <<== the internal representation
                                                                                                                           
scala> Logarithm(4).toDouble
val res1: Double = 4.0                                <<== as expected
                                                                                                                           
scala> Logarithm(4).toDouble.toString
val res2: String = 4.0                                <<== as expected
                                                                                                                           
scala> Logarithm(4).toString
val res3: String = 1.3862943611198906                 <<== expected: 4.0 !!!
                                                                                                                           
scala> Logarithm(4).toString2
val res4: String = 1.3862943611198906                 <<== expected: 4.0 !!!

How do I define toString without copying the code inside toDouble ?

UPD with

def toString: String = "log:"+x.asInstanceOf[Logarithm].toDouble.toString
def toString2: String = "log:"+x.asInstanceOf[Logarithm].toDouble.toString

I get

scala> Logarithm(4).toString
val res0: String = 1.3862943611198906               <<== expected: log:4.0
                                                                                                                           
scala> Logarithm(4).toString2
val res1: String = log:1.3862943611198906           <<== expected: log:4.0

Upvotes: 0

Views: 258

Answers (1)

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

Regarding the part of your question

How do I define toString without copying the code inside toDouble?

Just don't call it "toDouble". In the context where the opaque type is known to be Double, the toDouble method will be interpreted as just the usual Double#toDouble (i.e. it will do nothing). If you rename your toDouble to something like toLinear (for internal use inside of toString2), then the toLinear will not collide with the built-in toDouble, and your toString2 works as expected:

opaque type Logarithm = Double

object Logarithm:
  def apply(d: Double): Logarithm = math.log(d)

extension (x: Logarithm)
  private def toLinear: Double = math.exp(x)
  def toDouble: Double = toLinear
  def toString2: String = s"Logarithm(${x.toLinear.toString})"

println(Logarithm(4.0).toString2) // Logarithm(4.0)

You can't do anything about the toString though, because toString is defined on AnyRef / java.lang.Object, and you just can't get rid of it. You could try to use a Show typeclass instead, depending on how well it would fit into your project.

Upvotes: 2

Related Questions