0

I have something like:

trait Node[P <: Node[_]]

class RootNode extends Node[Null] {
   val refB : NodeB[RootNode] = ....
}

class NodeB[P <: Node[_]] extends Node[P] {
   val refC : NodeC[NodeB[P]] = ....
}

class NodeC[P <: Node[_]] extends Node[P] {
   val refD : NodeD[NodeC[P]] = ....
}

Is there a better way to deal with this sort of structure? Somehow, with my approach, we could use P to constraint only to the immediate parent Level, but we have lost the parent of the parent (and so on), and therefore the constrain wouldn't be all the tight that it could be. If do not want to loose all the context I would have to change it to something like:

class NodeC[P <: Node[_]] extends Node[P] {
   val refD : NodeD[NodeC[NodeB[NodeA[RootNode]]] = ....
}

which is completely infeasible.

Any approach that I have tried has lead me to an illegal cyclic reference. Is there any solution to this problem?

lqbweb
  • 1,684
  • 3
  • 19
  • 33

1 Answers1

1

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))
          )
    }
  }
flavian
  • 28,161
  • 11
  • 65
  • 105
  • Thanks for your suggestion. I already thought about doing something like a wrapper, not so much like a List, but like a tree (the same thing in the end), where every Node would be a container for other Tree entries... But then you need to unwrap in order to access the element itself, and using it sounds like a bit more complex. Also, I have to admit that reaching just a parent level as I suggested initially in my post is perfectly fine for requirements, but not 100% ideal, and just for the sake of knowing I would like to find a better solution without the need of any further wrapper. – lqbweb Jul 28 '16 at 17:03