我正在研究scala语言和成熟的代数类型系统的兼容性,这是一个有趣的案例:
假设我有一个复杂的CC类型,它是AA和BB的乘积:
case class AA(_1: Int, _2: Int)
case class BB(_3: Int)
case class CC(a: AA, b: BB)
现在我想定义一个类型代数 flatten(.),它可以在其操作数的定义中展平所有内部积类型,例如 flatten(CC) 可以产生以下产品类型定义:
case class FCC(_1: Int, _2: Int, _3: Int)
在 scala 2.12+ 中实现这样的东西有多难?欢迎您使用任何可能的技巧,包括但不限于:宏观,斯卡拉兹,猫,无形等。
非常感谢您的意见!
shapeless
是可能的,但不是微不足道的。这是一个不完整的草图,很可能必须进行调整。
声明所需的接口。我们将把树压平成HList
import shapeless._
trait Flattener[A] {
type Result <: HList
def flatten(input: A): Result
}
object Flattener {
type Aux[T, R] = Flattener[T] { type Result = R }
}
def flatten[A](input: A)(implicit ev: Flattener[A]): ev.Result = {
ev.flatten(input)
}
现在我们有 3 种情况:A
要么是非产品类型,要么直接编组到单元素列表中:
implicit def nonProdFlattener[T : NotAProduct] : Flattener.Aux[T, (T :: HNil)] = {
new Flattener[T] {
override type Result = (T :: HNil)
override def flatten(input: T) = HList(input)
}
}
在这里,我们需要证明T
本身不是产品。shapeless
没有开箱即用的 (AFAIK),但您可以使用此技巧: Scala:强制执行 A 不是 B 的子类型
第二种情况:P
是T
型单元素产品,我们已经知道如何将T
转换为R
implicit def oneElementProduct[T, R, P <: Product](implicit ev: Aux[P, T :: HNil], fl: Flattener.Aux[T, R]): Flattener.Aux[T, R] = ???
最后一种情况:P
是从H
开始,然后是T
(这是HList
)
implicit def multiElemProduct[P, R1, R2, H, T](implicit ev: Aux[P, H :: T], fl1: Flattener.Aux[H, R1], fl2: Flattener.Aux[T, R2]):
Flattener.Aux[P, R1 :: R2] = ???
然后,如果要转换为预定义的FCC
而不是HList
则需要Generic
(https://www.scala-exercises.org/shapeless/generic) 才能将HList
转换为案例类。