В настоящее время я играю с системой типов scala, пытаясь написать DSL. Я бы хотел следующее:

// custom dsl
val out = TagA(
  TagB(),
  TagC(
    TagC()
    TagA()
  ),
)
  • TagA принимает параметры 0..n TagB или TagC, но не TagA
  • TagB не принимает параметров
  • TagC принимает параметры 0..n TagC или TagA, но не TagB

Итак, в основном я пытаюсь передать различные типы дизъюнктов в параметре varargs. Я подумал, что могу ограничить конкретные типы, которые принимает каждый Tag, используя классы типов. Поэтому я смоделировал TagA, TagB и TagC следующим образом:

case class TagA[A: TagAChildren](children: A*)
case object TagB
case class TagC[A: TagCChildren](children: A*)


sealed trait TagAChild[-T]

object TagAChildren {
  implicit object TagBWitness extends TagAChild[TagB]
  implicit object TagCWitness extends TagAChild[TagC]
}


sealed trait TagCChild[-T]

object TagCChildren {
  implicit object TagCWitness extends TagCChild[TagC]
  implicit object TagAWitness extends TagCChild[TagA]
}

Теперь это отлично работает, если я передаю один тип. Например:

TagA()     // compiles

TagA(      // compiles
  TagB(),
  TagB(),
)

Однако компиляция не удастся, если я смешаю, например, TagB и TagC:

TagA(
  TagB(),
  TagC(),
)

// could not find implicit value for evidence parameter of type TagAChild[Product with java.io.Serializable]

Очевидно, что компилятор не может найти неявный. Думаю, я слишком многого просил у компилятора ... Кто-нибудь может придумать что-нибудь, чтобы заставить эту работу работать? Любой намек или альтернативный подход очень приветствуются.

0
Roman 12 Фев 2021 в 15:19

1 ответ

Лучший ответ

Итак, вот что я придумал:

case class TagA(children: TagMagnet[TagA]*)
case object TagB
case class TagC(children: TagMagnet[TagC]*)

sealed trait TagMagnet[A] {
  type Result
  def apply(): Result
}

trait TagMagnetBuilder[A] {
  def build[R](value: R): TagMagnet[A] = new TagMagnet[A] {
    override type Result = R
    override def apply(): Result = value
  }
}

object TagAMagnet extends TagMagnetBuilder[TagA] {
  implicit def tagBMagnet(tag: TagB): TagMagnet[TagA] = build(tag)
  implicit def tagCMagnet(tag: TagC): TagMagnet[TagA] = build(tag)
}

object TagCMagnet extends TagMagnetBuilder[TagC] {
  implicit def tagCMagnet(tag: TagC): TagMagnet[TagC] = build(tag)
  implicit def tagAMagnet(tag: TagA): TagMagnet[TagC] = build(tag)
}

Для любого заданного типа Tag я могу просто расширить TagMagnetBuilder и определить, какие типы этот тег должен принимать. С приведенными выше определениями следующий фрагмент компилируется без проблем:

val out = TagA(
  TagB(),
  TagC(
    TagC()
    TagA()
  ),
)
1
Roman 13 Фев 2021 в 12:58