title: Scala 3 "Module Abstractions" chapter in the book
body: <p>I am trying to use the 2nd approach (&quot;Module Abstractions&quot;) from <a href="https://docs.scala-lang.org/scala3/book/types-opaque-types.html" rel="nofollow noreferrer">the &quot;Opaque Types&quot; chapter</a> of the Scala3 book. The code is intended to show how one can make Scala use a logarithmic number representation.</p>
  <p><em><strong>The problem is that I do not see how the code from the book may be used. For me, it just does not work.</strong></em></p>
  <pre><code>trait Logarithms:
  
    type Logarithm
  
    // operations on Logarithm
    def add(x: Logarithm, y: Logarithm): Logarithm
    def mul(x: Logarithm, y: Logarithm): Logarithm
  
    // functions to convert between Double and Logarithm
    def make(d: Double): Logarithm
    def extract(x: Logarithm): Double
  
    // extension methods to use `add` and `mul` as &quot;methods&quot; on Logarithm
    extension (x: Logarithm)
      def toDouble: Double = extract(x)
      def + (y: Logarithm): Logarithm = add(x, y)
      def * (y: Logarithm): Logarithm = mul(x, y)
      // I added
      def myToDouble: Double = extract(x)
  
  
  object LogarithmsImpl extends Logarithms:
  
    type Logarithm = Double
  
    // operations on Logarithm
    def add(x: Logarithm, y: Logarithm): Logarithm = make(x.toDouble + y.toDouble)
    def mul(x: Logarithm, y: Logarithm): Logarithm = x + y
  
    // functions to convert between Double and Logarithm
    def make(d: Double): Logarithm = math.log(d)
    def extract(x: Logarithm): Double = math.exp(x) 
  
  // I added
  object Logarithms:
    def apply(d: Double) = LogarithmsImpl.make(d)
  </code></pre>
  <p>The book says:</p>
  <blockquote>
  <p>However, this abstraction is slightly leaky. We have to make sure to
  only ever program against the abstract interface <code>Logarithms</code> and never
  directly use <code>LogarithmsImpl</code>. Directly using <code>LogarithmsImpl</code> would make
  the equality <code>Logarithm = Double</code> visible for the user, who might
  accidentally use a <code>Double</code> where a logarithmic double is expected.</p>
  </blockquote>
  <p>And now I try to use it:</p>
  <pre><code>$ ~/Downloads/scala3-3.1.3/bin/scala
  Welcome to Scala 3.1.3 (14.0.1, Java OpenJDK 64-Bit Server VM).
  Type in expressions for evaluation. Or try :help.
                                                                                                                             
  scala&gt; :load my/scala/logarithms1.scala
  // defined trait Logarithms
  // defined object LogarithmsImpl
  // defined object Logarithms
                                                                                                                             
  scala&gt; import Logarithms.*
                                                                                                                             
  scala&gt; Logarithms(4)
  val res0: LogarithmsImpl.Logarithm = 1.3862943611198906
                                                                                                                             
  scala&gt; Logarithms(2)
  val res1: LogarithmsImpl.Logarithm = 0.6931471805599453
                                                                                                                             
  scala&gt; Logarithms(4).toDouble
  val res2: Double = 1.3862943611198906
                                                                                                                             
  scala&gt; Logarithms(4).myToDouble
  -- [E008] Not Found Error: ----------------------------------------------------------------------------------------------------
  1 |Logarithms(4).myToDouble
    |^^^^^^^^^^^^^^^^^^^^^^^^
    |value myToDouble is not a member of LogarithmsImpl.Logarithm, but could be made available as an extension method.
    |
    |The following import might fix the problem:
    |
    |  import LogarithmsImpl.myToDouble
    |
  1 error found
  </code></pre>
  <p>In fact, importing LogarithmsImpl does not help much:</p>
  <pre><code>scala&gt; import LogarithmsImpl.*
                                                                                                                             
  scala&gt; import Logarithms.*
                                                                                                                             
  scala&gt; val ll: Logarithm = make(4.0)
  val ll: LogarithmsImpl.Logarithm = 1.3862943611198906
                                                                                                                             
  scala&gt; val lll: Logarithm = make(2.0)
  val lll: LogarithmsImpl.Logarithm = 0.6931471805599453
                                                                                                                             
  scala&gt; lll+ll
  val res0: Double = 2.0794415416798357
                                                                                                                             
  scala&gt; extract(lll+ll)
  val res1: Double = 7.999999999999998
  </code></pre>
  <p>The last line shows that the addition was the traditional floating-point addition rather than the override from the extension.</p>
  <p><em><strong>What am I missing?</strong></em></p>
  <p><strong>UPD</strong> One more problem in the book's code: the line</p>
  <pre><code>  def add(x: Logarithm, y: Logarithm): Logarithm = make(x.toDouble + y.toDouble)
  </code></pre>
  <p>should be</p>
  <pre><code>  def add(x: Logarithm, y: Logarithm): Logarithm = make(x.myToDouble + y.myToDouble)
  </code></pre>
  <p>or, even better:</p>
  <pre><code>  def add(x: Logarithm, y: Logarithm): Logarithm =  x + math.log1p(math.exp(y-x))
  </code></pre>
  <p><strong>UPD2</strong> and it looks like the trait and the objects are not companions, a trait is not a class, only classes may be companions.</p>