Scala 3中带Numeric/Ordering实例的不透明类型正确配置
问题场景与代码
定义底层为Int的不透明类型Ordinate,希望复用Int的Ordering和Numeric方法,无需额外导入就能直接使用<=这类操作符,但编译报错。
不透明类型定义
package world opaque type Ordinate = Int given Ordering[Ordinate] with { def compare(x: Ordinate, y: Ordinate): Int = x.compare(y) }
业务代码
package world import Ordinate.given class Boundary(dims: List[(Ordinate, Ordinate)]) { def contains(o: Ordinate, dimension: Int): Boolean = { val (min, max) = dims(dimension) min <= o && o <= max } }
编译错误
value <= is not a member of world.Ordinate, but could be made available as an extension method.
One of the following imports might fix the problem:
import world.given_Ordering_Ordinate.mkOrderingOps
import math.Ordering.Implicits.infixOrderingOps
import math.Ordered.orderingToOrdered
解决方案
要实现同包及全局无需额外导入即可使用操作符,核心是让Ordering的扩展方法(如<=)和Ordinate的given实例处于同一可见作用域。以下是几种可行方案:
1. 包级导入Ordering隐式扩展
在Ordinate的定义所在包内,直接导入Ordering的infix操作扩展,同包内所有代码自动可用:
package world import math.Ordering.Implicits.infixOrderingOps opaque type Ordinate = Int given Ordering[Ordinate] = Ordering.Int // 直接复用Int的Ordering实例,无需手动实现compare
此时同包内的Boundary类无需额外导入,就能直接使用<=操作。
2. 用对象封装并导出所有依赖
将Ordinate的定义、given实例和所需扩展方法封装到对象中,通过export暴露,方便全局引用:
package world object Ordinate { opaque type Ordinate = Int // 复用Int的Ordering实例 given Ordering[Ordinate] = Ordering.Int // 导出Ordering的infix操作符 export math.Ordering.Implicits.infixOrderingOps // 如果需要复用Numeric方法,同样导出Numeric的扩展 export math.Numeric.Implicits.infixNumericOps given Numeric[Ordinate] = Numeric.Int } // 同包内使用时,导入所有内容 import Ordinate.* class Boundary(dims: List[(Ordinate, Ordinate)]) { def contains(o: Ordinate, dimension: Int): Boolean = { val (min, max) = dims(dimension) min <= o && o <= max } }
若要让整个代码库无需手动导入就能使用,可在包对象中导出:
package world package object world { export Ordinate.* }
这样任何导入world._的地方,都能直接使用Ordinate及其所有操作符。
3. 全局隐式扩展(谨慎使用)
如果确实需要全局范围内无需任何导入就能使用,可将Ordering的扩展导入到根包,但这种方式可能引入命名冲突,建议仅在特定场景下使用:
package root import math.Ordering.Implicits.infixOrderingOps
Scala 3与Scala 2的差异说明
Scala 2中值类的隐式可直接通过伴生对象全局生效,而Scala 3的不透明类型更强调显式的作用域管理。零成本类型抽象依然可以实现,但显式导出隐式和扩展方法更符合Scala 3的设计理念,既能保证类型安全,又能避免不必要的全局隐式污染。
内容的提问来源于stack exchange,提问作者Rob Fitzgerald

