Scala3模块抽象实现对数表示遇问题,求技术解答
我尝试使用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

