Набор уровня типа для алгебраического типа данных

Это частично дублирует этот вопрос: Получение подклассов запечатанного признака, но ответ предлагает отражение во время выполнения, которое мне не подходит, и я хотел бы знать, возможно ли это во время компиляции, возможно, с использованием 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 не будет построен с более чем одним Encode, DistKey или другим 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


person chuwy    schedule 21.06.2016    source источник
comment
Может быть, здесь не хватает сути, но добавить собственный метод применения для столбца? Подтверждение строительства?   -  person Rhys Bradbury    schedule 21.06.2016
comment
Ну да, я уже думал об этом. Поскольку существует слишком много способов создать недопустимое значение (например, отрицательное step), даже не принимая во внимание эти наборы, поэтому в целом это, вероятно, того не стоит. Но вопрос остается в силе просто из любопытства.   -  person chuwy    schedule 21.06.2016
comment
Я не уверен, что вы пытаетесь, но поскольку вы упоминаете бесформенный и даже приводите пример: этот HList of Options на самом деле не является справедливым представлением ADT. HLists - это продукты, вам нужен дополнительный продукт: github.com/milessabin/shapeless/wiki/   -  person pedrofurla    schedule 21.06.2016
comment
Привет @pedrofurla! Для меня это все еще больше похоже на продукт. Поскольку сопродукт предполагает, что у нас есть значение, которое является DistKey или Encode или другим ColumnAttribute, но мне нужно (вероятно, пустое) произведение необязательных DistKey и необязательно Encode и т. д.   -  person chuwy    schedule 22.06.2016


Ответы (1)


Если мы представляем columnAttributes в Column как HList, нам сначала нужно ограничить HList элементы, чтобы они были подтипами ColumnAttribute и были отличными.

Мы можем использовать ограничения hlist LUBConstraint и IsDistinctConstraint для этого.

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

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

Теперь нам нужно ограничить HList, чтобы он не мог содержать одновременно Encode и DistKey, к сожалению, нам нужно написать этот класс типа самостоятельно.

Мы можем использовать =:= для проверки первого элемента и 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)
person Peter Neyens    schedule 21.06.2016
comment
Спасибо, @PeterNeyens. Это частично отвечает на мой вопрос. Отчасти потому, что, кажется, я сформулировал это недостаточно четко (обновленная тема). Но это удовлетворяет мое любопытство и выглядит потрясающе. - person chuwy; 22.06.2016
comment
С вашим обновленным вопросом кажется, что вам не нужно ограничение OnlyOneOf, а нужны просто LUB и IsDistinct. - person Peter Neyens; 22.06.2016