2

Это частично дубликат в этом вопросе: 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?

Более конкретный вопрос (возможно, я искал неправильное решение). Имея над АСТ плюс следующий класс, как я могу быть уверен во время компиляции, что 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

+0

Возможно, здесь отсутствует пункт здесь, но добавьте специальный метод подачи для столбца? Подтверждение строительства? –

+0

Ну да, я уже думал об этом. Потому что существует слишком много способов построить недопустимое значение (например, отрицательный «шаг»), даже не принимая во внимание эти множества, поэтому в целом это, вероятно, не стоит. Но вопрос все еще справедлив только ради любопытства. – chuwy

+3

Я не уверен, что вы пытаетесь, но так как вы упоминаете бесформенность и даже приводите пример: HList of Options на самом деле не является справедливым представлением ADT. HLists - это продукты, вам нужен копродукт: https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#coproducts-and-discriminated-unions – pedrofurla

ответ

3

Если мы представим columnAttributes в Column как 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, к сожалению, мы должны написать этот тип класса сами.

Мы можем использовать =:= для проверки первого элемента и 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) 
+0

Спасибо @PeterNeyens. Это отчасти отвечает на мой вопрос. Отчасти потому, что я сформулировал это недостаточно ясно (обновленная тема). Но это удовлетворяет мое любопытство и выглядит как удивительный клочок Безжизненного. – chuwy

+1

С обновленным вопросом, кажется, вам не нужно ограничение 'OnlyOneOf', и вам просто нужны' LUB' и 'IsDistinct'. –

Смежные вопросы