代数数据类型的类型级集
Type-level Set for Algebraic Data Type
这个问题部分重复:,但答案建议运行时反射,这对我来说不合适,我想知道是否可以在编译时使用 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 加上下面的 class,我怎么能在编译时确定 Column
不会用多个 Encode
或 DistKey
或其他 [=16] 构造=].
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 constraintsLUBConstraint
和IsDistinctConstraint
来实现。
import shapeless.{Default => _, _}
import LUBConstraint._
import IsDistinctConstraint._
def acceptOnlyDistinctAttributes
[L <: HList : <<:[ColumnAttribute]#λ : IsDistinctConstraint](l: L): L = l
现在我们需要约束HList
,所以它不能同时包含Encode
和DistKey
,不幸的是我们需要自己写这个类型class。
我们可以使用 =:=
检查第一个元素,使用 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
并且不会同时包含 Encode
和 DistKey
:
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)
这个问题部分重复:
所以,有了这个 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 加上下面的 class,我怎么能在编译时确定 Column
不会用多个 Encode
或 DistKey
或其他 [=16] 构造=].
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 constraintsLUBConstraint
和IsDistinctConstraint
来实现。
import shapeless.{Default => _, _}
import LUBConstraint._
import IsDistinctConstraint._
def acceptOnlyDistinctAttributes
[L <: HList : <<:[ColumnAttribute]#λ : IsDistinctConstraint](l: L): L = l
现在我们需要约束HList
,所以它不能同时包含Encode
和DistKey
,不幸的是我们需要自己写这个类型class。
我们可以使用 =:=
检查第一个元素,使用 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
并且不会同时包含 Encode
和 DistKey
:
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)