Reputation: 16832
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
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