Roman
Roman

Reputation: 5699

Restrict varargs parameter to certain disjunct types

I'm currently playing with the scala type system trying to write a DSL. What I'd like to have is the following:

// custom dsl
val out = TagA(
  TagB(),
  TagC(
    TagC()
    TagA()
  ),
)

So basically I'm trying to pass various disjunct types in a varargs parameter. I thought I could restrict the concrete types that each Tag is accepting using typeclasses. Therefore I modeled TagA, TagB and TagC as follows:

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]
}

Now this works fine if I'm passing a single type. For example:

TagA()     // compiles

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

However compilation fails if I mix for example TagB and TagC:

TagA(
  TagB(),
  TagC(),
)

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

Obviously the compailer fails to find an implicit. I guess I asked a bit too much from the compiler here... Can anyone think of something to make this work? Any hint or alternative approach is very much apprechiated.

Upvotes: 0

Views: 54

Answers (1)

Roman
Roman

Reputation: 5699

So here is what I came up with:

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)
}

For any given Tag type I can just extend TagMagnetBuilder and define what types this tag should accept. With the above definitions in place the following piece compiles without issues:

val out = TagA(
  TagB(),
  TagC(
    TagC()
    TagA()
  ),
)

Upvotes: 1

Related Questions