scala - 代数数据类型的类型级别集

标签 scala abstract-syntax-tree shapeless type-level-computation

这与此问题部分重复:Getting subclasses of a sealed trait ,但答案表明运行时反射这对我来说不合适,我想知道在编译时是否可能,可能使用 Shapeless。

所以,有了这个 ADT:

sealed trait ColumnAttribute
case class Default(value: String) extends ColumnAttribute
case class Identity(seed: Int, step: Int) extends ColumnAttribute
case class Encode(encoding: CompressionEncoding) extends ColumnAttribute
case object DistKey extends ColumnAttribute

如何获得类似 Option[Default]::Option[Identity]::Option[Encode]::Option[DistKey]::HNil 的内容?

更具体的问题(可能我正在寻找错误的解决方案)。 有了上面的 AST 加上下面的类,我如何在编译时确定 Column 不会使用多个 EncodeDistKey 构建或其他ColumnAttribute

case class Column(columnName: String, dataType: DataType, columnAttributes: Set[ColumnAttribute], columnConstraints: Set[ColumnConstraint])

UPD:columnAttributes 应仅包含特定子类型的一个值,但可以包含不同子类型的多个值。

所以,这个伪代码应该是正确的:

columnConstraint = Default("foo")::DistKey::Identity(1,2)::HNil

但这应该会失败:

columnConstraint = Default("foo")::Default("bar")::HNil

最佳答案

如果我们将 Column 中的 columnAttributes 表示为 HList,我们首先需要将 HList 元素约束为是 ColumnAttribute 的子类型并且是不同的。

我们可以使用hlist constraints LUBConstraintIsDistinctConstraint 来实现此目的。

import shapeless.{Default => _, _}
import LUBConstraint._
import IsDistinctConstraint._

def acceptOnlyDistinctAttributes
  [L <: HList : <<:[ColumnAttribute]#λ : IsDistinctConstraint](l: L): L = l

现在我们需要约束HList,使其不能同时包含EncodeDistKey,不幸的是我们需要自己编写这个类型类.

我们可以使用=:=来检查第一个元素,并使用NotContainsConstraint来检查尾部是否不包含其他类型。

trait OnlyOneOfConstraint[L <: HList, A, B] extends Serializable

object OnlyOneOfConstraint {

  def apply[L <: HList, A, B]
    (implicit ooo: OnlyOneOfConstraint[L, A, B]): OnlyOneOfConstraint[L, A, B] = ooo

  type OnlyOneOf[A, B] = {
    type λ[L <: HList] = OnlyOneOfConstraint[L, A, B]
  }

  implicit def hnilOnlyOneOf[A, B] = new OnlyOneOfConstraint[HNil, A, B] {}

  // head is A, so tail cannot contain B
  implicit def hlistOnlyOneOfA[H, T <: HList, A, B](implicit
    ncB: T NotContainsConstraint B, 
    eq: A =:= H,
    oooT: OnlyOneOfConstraint[T, A, B]
  ) = new OnlyOneOfConstraint[H :: T, A, B] {}

  // head is B, so tail cannot contain A
  implicit def hlistOnlyOneOfB[H, T <: HList, A, B](implicit
    ncA: T NotContainsConstraint A, 
    eq: B =:= H,
    oooT: OnlyOneOfConstraint[T, A, B]
  ) = new OnlyOneOfConstraint[H :: T, A, B] {}

  // head is not A or B
  implicit def hlistOnlyOneOf[H, T <: HList, A, B](implicit
    neqA: A =:!= H,
    neqB: B =:!= H,
    oooT: OnlyOneOfConstraint[T, A, B]
  ) = new OnlyOneOfConstraint[H :: T, A, B] {}
}

现在我们可以使用这些约束编写(简化的)Column:

type CompressionEncoding = String

sealed trait ColumnAttribute
case class Default(value: String) extends ColumnAttribute
case class Identity(seed: Int, step: Int) extends ColumnAttribute
case class Encode(encoding: CompressionEncoding) extends ColumnAttribute
case object DistKey extends ColumnAttribute

import OnlyOneOfConstraint._

case class Column[
  Attrs <: HList 
    : <<:[ColumnAttribute]#λ 
    : IsDistinctConstraint 
    : OnlyOneOf[Encode, DistKey.type]#λ
](columnAttributes: Attrs)

我们现在有一个编译时保证,属性是不同的 ColumnAttributes 并且不会同时包含 EncodeDistKey :

Column(DistKey :: Default("s") :: HNil)
// Column[shapeless.::[DistKey.type,shapeless.::[Default,shapeless.HNil]]] = Column(DistKey :: Default(s) :: HNil)

Column(Default("s") :: Encode("a") :: HNil)
// Column[shapeless.::[Default,shapeless.::[Encode,shapeless.HNil]]] = Column(Default(s) :: Encode(a) :: HNil)

Column(DistKey :: Default("s") :: Encode("a") :: HNil)
// <console>:93: error: could not find implicit value for evidence parameter of type OnlyOneOfConstraint[shapeless.::[DistKey.type,shapeless.::[Default,shapeless.::[Encode,shapeless.HNil]]],Encode,DistKey.type]
//        Column(DistKey :: Default("s") :: Encode("a") :: HNil)

关于scala - 代数数据类型的类型级别集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37949638/

相关文章:

scala - 使用带有 LabelledGenerics 的无形状标签

java - Scala如何将 "None"排序到底部(如果存在)并选择每组中的第一行?

java - 将 antlr AST 用于具有重复节点的 switch case 的 java 代码

pretty-print - 如何使用 ANTLR4 漂亮地打印作品和行号?

scala - 用函数替换异步匿名类

scala - 检查无形中可扩展记录之间的子类型关系

xml - Scala/Lift 中是否(或是否)有 XML 和 JSON 可序列化对象的标准特征?

algorithm - Scala 中高效的最近邻搜索

python - 将 Python 作为子进程调用时,我可以强制它以交互模式运行吗?

haskell - 比较语法树模 alpha 转换