This is partly duplicate of this question: Getting subclasses of a sealed trait , but answer suggests runtime-reflection which is inappropriate for me and I would like to know if it is possible on compilation-time, probably using Shapeless.
So, having this 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
How can I get something like Option[Default] :: Option[Identity] :: Option[Encode] :: Option[DistKey] :: HNil
?
More specific question (probably I'm looking for wrong solution). Having above AST plus following class, how can I be sure in compile-time that Column
will not get constructed with more than one Encode
or DistKey
or other ColumnAttribute
.
case class Column(columnName: String, dataType: DataType, columnAttributes: Set[ColumnAttribute], columnConstraints: Set[ColumnConstraint])
UPD : columnAttributes
should contain only one value of particular subtype, but can contain several values of distinct subtypes.
So, this pseudo-code should be correct:
columnConstraint = Default("foo") :: DistKey :: Identity(1,2) :: HNil
But this should fail:
columnConstraint = Default("foo") :: Default("bar") :: HNil
If we represent columnAttributes
in Column
as an HList
, we first need to constrain the HList
elements to be subtypes of ColumnAttribute
and be distinct.
We can use the hlist constraints LUBConstraint
and IsDistinctConstraint
to achieve this.
import shapeless.{Default => _, _}
import LUBConstraint._
import IsDistinctConstraint._
def acceptOnlyDistinctAttributes
[L <: HList : <<:[ColumnAttribute]#λ : IsDistinctConstraint](l: L): L = l
Now we need to constraint the HList
so it can not contain both Encode
and DistKey
, unfortunately we need to write this type class ourselves.
We can use =:=
to check the first element and the NotContainsConstraint
to check if the tail doesn't contain the other type.
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] {}
}
Now we can write (a simplified) Column
using these constraints :
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)
We now have a compile time guarantee that the attributes are distinct ColumnAttributes
and will not contain not both a Encode
and a 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)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.