Have you considered representing the restriction as a different kind of structure? That kind of nesting is indeed possible, however it looks like the kind of concern you could implement using HList
.
Instead of representing refs in that fashion, why not simply your implementation of Node
by using something like the following. I'm offering some generic examples here of what could be done with super simple shapeless patterns.
If you can be more specific about the requirements, I'm sure many here can do more to help, my gut tells me there's a simpler approach around HList
that can answer your problem without any nasty type juggling.
import shapeless._
import shapeless.ops.hlist._
import shapeless.::
class Node[P <: Hlist](hl: P) {
def append[T](obj: T): Node[P :: T] = new Node[P :: T](hl :: obj)
// Much like a normal List, HList will prepend by default.
// Meaning you need to reverse to get the input order.
def reverse[Out]()(
implicit rev: Reverse.Aux[P, Out]
): Out = rev(hl)
// you can enforce type restrictions with equality evidence.
// For instance you can use this to build a chain
// and then make sure the input type matches the user input type.
def equalsFancy[V1 <: Product, Rev, Out <: Product](v1: V1)(
// We inverse the type of the HList to destructure it
// and get the initial order.
implicit rev: Reverse.Aux[P, Rev],
// then convert it to a tuple for example.
tp: Tupler.Aux[Rev, Out],
ev: V1 =:= Out
): Boolean = tp(hl) == v1
}
object Node {
def apply: Node[HNil] = new Node[HNil]
Node().append[String]("test").append[Int](5).equalsFancy("test" -> 5)
}
It's quite easy to restrict the type element in your list to only subtype of Node
by using an LUBConstraint
as well.(lower upper bound for the type).
class NodeList[HL <: HList](list: Node[_] :: HL)(implicit val c: LUBConstraint[HL, Node[_])
This would mean you can no longer append elements that are not _ <:< Node[_]
to the NodeList
which could give you some Poly
niceties.
trait A
trait B
object printPoly extends Poly1 {
// Let's assume these are your A, B and Cs
// You can use Poly to define type specific behaviour.
implicit def caseNodeA[N <: Node[A]] = at[N](node => println("This is an A node"))
implicit def caseNodeB[N <: Node[B]] = at[N](node => println("This is a B node"))
implicit def unknown[N <: Node[_]] = at[N](node => println("This is not known to us yet"))
}
val nodeList: NodeList[..] = ..
nodeList.list.map(printPoly)
Update
It's worth implementing a Tree like structure then.
case class Node[A, F <: HList](value: A, children: F) {
def addChild[T, FF <: HList](
child: Node[T, FF]
): Node[A, HTree[T, FF] :: F] = {
new Node(value, child :: children)
}
def values = Node.Values(this)
}
object Node {
def apply[A](label: A) = new Node[A, HNil](label, HNil)
object Values extends Poly1 {
implicit def caseHTree[A, F <: HList, M <: HList](
implicit fm: FlatMapper.Aux[getLabels.type, F, M],
prepend: Prepend[A :: HNil, M]
): Case.Aux[HTree[A, F], prepend.Out] =
at[HTree[A, F]](tree => prepend(
tree.value :: HNil,
fm(tree.children))
)
}
}