You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala3模块抽象实现对数表示遇问题,求技术解答

Scala3 《Opaque Types》模块抽象实现对数数值的问题解析

我尝试使用Scala3书籍《Opaque Types》章节中的第二种方法「Module Abstractions」实现对数数值表示,但代码无法正常工作:扩展方法myToDouble找不到,自定义加法逻辑未生效,反而执行了传统浮点加法。此外发现书籍代码中add方法存在错误,且特质与对象并非伴生关系。

代码实现

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 "methods" on Logarithm
  extension (x: Logarithm)
    def toDouble: Double = extract(x)
    def + (y: Logarithm): Logarithm = add(x, y)
    def * (y: Logarithm): Logarithm = mul(x, y)
    // 新增的扩展方法
    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) 

// 自定义的工厂对象
object Logarithms:
  def apply(d: Double) = LogarithmsImpl.make(d)

书中说明

不过这种抽象存在轻微的泄漏问题。我们必须确保始终仅针对抽象接口Logarithms编程,绝不要直接使用LogarithmsImpl。直接使用LogarithmsImpl会让用户看到Logarithm = Double的等价关系,可能导致用户意外地在需要对数Double的地方使用普通Double。

测试过程及问题

$ ~/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> :load my/scala/logarithms1.scala
// defined trait Logarithms
// defined object LogarithmsImpl
// defined object Logarithms

scala> import Logarithms.*

scala> Logarithms(4)
val res0: LogarithmsImpl.Logarithm = 1.3862943611198906

scala> Logarithms(2)
val res1: LogarithmsImpl.Logarithm = 0.6931471805599453

scala> Logarithms(4).toDouble
val res2: Double = 1.3862943611198906

scala> 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

导入LogarithmsImpl后问题仍未解决:

scala> import LogarithmsImpl.*

scala> import Logarithms.*

scala> val ll: Logarithm = make(4.0)
val ll: LogarithmsImpl.Logarithm = 1.3862943611198906

scala> val lll: Logarithm = make(2.0)
val lll: LogarithmsImpl.Logarithm = 0.6931471805599453

scala> lll+ll
val res0: Double = 2.0794415416798357

scala> extract(lll+ll)
val res1: Double = 7.999999999999998

最后一行显示加法执行的是传统浮点加法,而非扩展方法中定义的逻辑。

发现的代码问题

更新1:书籍代码中的add方法存在错误,原代码:

def add(x: Logarithm, y: Logarithm): Logarithm = make(x.toDouble + y.toDouble)

应修改为对数域加法的正确逻辑(对数相加等价于原数相乘):

def add(x: Logarithm, y: Logarithm): Logarithm = x + y

或数值稳定性更优的写法:

def add(x: Logarithm, y: Logarithm): Logarithm =  x + math.log1p(math.exp(y-x))

更新2:特质与对象并非伴生关系,Scala中只有类才能拥有伴生对象,特质没有伴生对象的概念。

问题根源与解决方案

你忽略了模块抽象的核心封装原则:必须通过抽象特质Logarithms的类型和方法进行编程,而非直接依赖具体实现LogarithmsImpl。具体问题和修复方式如下:

1. 扩展方法不可见的原因

特质Logarithms中的扩展方法属于该特质的成员,只有在混入该特质的上下文,或者明确导入特质的扩展方法时,才能被关联到Logarithm类型上。你之前直接使用LogarithmsImpl.Logarithm(本质是Double),Scala不会自动将特质的扩展方法绑定到原生Double类型,导致myToDouble找不到。

2. 自定义加法未生效的原因

当变量类型被推导为Double时,Scala会优先调用原生的+方法,而非特质中定义的扩展方法。只有当变量的类型是抽象的Logarithms.Logarithm时,才会触发扩展方法的匹配。

3. 修正后的代码

调整Logarithms对象,让它完全基于抽象特质封装,隐藏具体实现:

trait Logarithms:
  type Logarithm

  def add(x: Logarithm, y: Logarithm): Logarithm
  def mul(x: Logarithm, y: Logarithm): Logarithm
  def make(d: Double): Logarithm
  def extract(x: Logarithm): Double

  extension (x: Logarithm)
    def toDouble: Double = extract(x)
    def + (y: Logarithm): Logarithm = add(x, y)
    def * (y: Logarithm): Logarithm = mul(x, y)
    def myToDouble: Double = extract(x)

object LogarithmsImpl extends Logarithms:
  type Logarithm = Double

  // 对数域加法:log(a) + log(b) = log(a*b),直接返回x+y即可
  def add(x: Logarithm, y: Logarithm): Logarithm = x + y
  // 对数域乘法:log(a) * log(b) = log(a^log(b)),即x*y
  def mul(x: Logarithm, y: Logarithm): Logarithm = x * y

  def make(d: Double): Logarithm = math.log(d)
  def extract(x: Logarithm): Double = math.exp(x)

// 正确的工厂对象:混入Logarithms特质,委托具体实现
object Logarithms extends Logarithms:
  private val impl = LogarithmsImpl
  type Logarithm = impl.Logarithm
  def add(x: Logarithm, y: Logarithm): Logarithm = impl.add(x, y)
  def mul(x: Logarithm, y: Logarithm): Logarithm = impl.mul(x, y)
  def make(d: Double): Logarithm = impl.make(d)
  def extract(x: Logarithm): Double = impl.extract(x)
  // 扩展方法自动继承自Logarithms特质

4. 正确测试示例

scala> import Logarithms.*
scala> val a = make(4)
val a: Logarithms.Logarithm = 1.3862943611198906
scala> val b = make(2)
val b: Logarithms.Logarithm = 0.6931471805599453
scala> a + b
val res0: Logarithms.Logarithm = 2.0794415416798357
scala> extract(a + b)
val res1: Double = 7.999999999999998 // 对应4*2=8,符合预期
scala> a.myToDouble
val res2: Double = 4.0 // 扩展方法正常生效

内容的提问来源于stack exchange,提问作者18446744073709551615

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:36:32