Scala中如何在两个密封特质中复用递归样例类构造器?
Scala逻辑公式ADT复用样例类的解决方案
我在定义多个表示逻辑公式的ADT,它们都用到And这类构造器,但其他构造器存在差异。希望复用样例类定义实现后续代码复用,尝试了两种写法但均存在问题:
第一种写法(无法为参数指定单一类型):
sealed trait Formula sealed trait PositiveFormula case class Not(sub) extends Formula case class And(left, right) extends Formula, PositiveFormula
第二种尝试(语法不合法):
sealed trait Formula sealed trait PositiveFormula case class Not[A](sub : A) Not[Formula] extends Formula case class And(left : A, right : A) And[Formula] extends Formula And[PositiveFormula] extends PositiveFormula
针对上述场景,解答如下:
1. 是否存在可行的语法实现上述需求?
Scala没有直接让泛型样例类的特定实例化版本单独继承不同特质的语法,但可以通过特质混入+类型约束或合理设计ADT层级来近似实现需求。
比如让PositiveFormula成为Formula的子类型,再让And同时混入两个特质并指定参数类型:
sealed trait Formula sealed trait PositiveFormula extends Formula case class Not(sub: Formula) extends Formula case class And(left: PositiveFormula, right: PositiveFormula) extends PositiveFormula
如果两个特质是完全独立的,可以用泛型约束+自我类型来限定:
sealed trait Formula sealed trait PositiveFormula case class Not(sub: Formula) extends Formula case class And[F <: Formula](left: F, right: F) extends Formula { self: PositiveFormula => }
这种写法需要确保And的实例同时满足两个特质的要求,本质是通过类本身的混入实现“一个构造器属于多个ADT”的效果。
2. 有没有其他解决“复用样例类构造器”问题的方案?
有三种常用的替代方案:
- 提取通用构造器为抽象特质/类:把
And这类通用结构的逻辑抽离到公共特质,让不同ADT的具体构造器继承它,复用结构和方法:
trait BinaryFormula[F] { def left: F def right: F } sealed trait Formula sealed trait PositiveFormula case class Not(sub: Formula) extends Formula case class FormulaAnd(left: Formula, right: Formula) extends Formula with BinaryFormula[Formula] case class PositiveAnd(left: PositiveFormula, right: PositiveFormula) extends PositiveFormula with BinaryFormula[PositiveFormula]
- 使用类型类解耦:定义类型类描述“可组合的公式”,让
And作为类型类的方法,而非样例类,实现构造逻辑的复用:
trait Composable[F] { def and(left: F, right: F): F } sealed trait Formula case class Not(sub: Formula) extends Formula case class FormulaAnd(left: Formula, right: Formula) extends Formula sealed trait PositiveFormula case class PositiveAnd(left: PositiveFormula, right: PositiveFormula) extends PositiveFormula object ComposableInstances { implicit val formulaComposable: Composable[Formula] = (l, r) => FormulaAnd(l, r) implicit val positiveFormulaComposable: Composable[PositiveFormula] = (l, r) => PositiveAnd(l, r) }
- 嵌套ADT层级:让
PositiveFormula作为Formula的子类型,And直接接受PositiveFormula参数,自然同时属于两个ADT体系,逻辑更贴合领域模型:
sealed trait Formula sealed trait PositiveFormula extends Formula case class Not(sub: Formula) extends Formula case class And(left: PositiveFormula, right: PositiveFormula) extends PositiveFormula
3. 若该方案可行,其实用性如何?
不同方案的实用性取决于你的业务场景:
- 泛型+特质混入方案:适合简单的公式结构,写法简洁,能直接复用样例类定义,利用Scala模式匹配特性。但如果两个ADT无继承关系,会导致类型推断复杂,后续扩展构造器时容易出现类型边界冲突,维护成本上升。
- 类型类方案:解耦了构造逻辑与ADT本身,扩展性极强,新增公式类型时只需实现对应的类型类实例,无需修改原有代码,适合复杂多变的公式体系。
- 提取通用特质/嵌套ADT方案:更符合面向对象的复用逻辑,结构直观,模式匹配友好,适合需要在多个ADT中共享相同结构和方法的常规场景,实用性最高。
内容的提问来源于stack exchange,提问作者zaabson
相关产品推荐
相关产品推荐

