Reputation: 5699
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()
),
)
TagA
accepts 0..n
TagB
or TagC
parameters, but not TagA
TagB
accepts no parametersTagC
accepts 0..n
TagC
or TagA
parameters, but not TagB
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
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