简体   繁体   中英

Type-level Set for Algebraic Data Type

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM