Возможна цепочка более высокого рода?

У меня есть что-то вроде:

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

Есть ли лучший способ справиться с такой структурой? Каким-то образом, с моим подходом, мы могли бы использовать P для ограничения только непосредственного родительского уровня, но мы потеряли родителя родителя (и так далее), и поэтому ограничение не было бы настолько жестким, каким могло бы быть. Если я не хочу терять весь контекст, мне придется изменить его на что-то вроде:

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

что совершенно невыполнимо.

Любой подход, который я пробовал, приводил меня к недопустимой циклической ссылке. Есть ли решение этой проблемы?


person lqbweb    schedule 27.07.2016    source источник


Ответы (1)


Рассматривали ли вы представление ограничения в виде структуры другого типа? Такое вложение действительно возможно, однако похоже на то, что вы могли бы реализовать с помощью HList.

Вместо того, чтобы представлять ссылки таким образом, почему бы просто не реализовать Node, используя что-то вроде следующего. Я предлагаю несколько общих примеров того, что можно сделать с очень простыми бесформенными узорами.

Если вы можете уточнить требования, я уверен, что многие здесь могут сделать больше, чтобы помочь, моя интуиция подсказывает мне, что есть более простой подход к HList, который может решить вашу проблему без какого-либо неприятного жонглирования типами.

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

Довольно легко ограничить элемент типа в вашем списке только подтипом Node, используя также LUBConstraint (нижняя верхняя граница для типа).

class NodeList[HL <: HList](list: Node[_] :: HL)(implicit val c: LUBConstraint[HL, Node[_])

Это означало бы, что вы больше не можете добавлять элементы, которые не являются _ <:< Node[_], к NodeList, что может дать вам некоторые Poly тонкости.

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)

Обновить

Тогда стоит реализовать древовидную структуру.

  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))
          )
    }
  }
person flavian    schedule 27.07.2016
comment
Спасибо за ваше предложение. Я уже думал о том, чтобы сделать что-то вроде обертки, не столько как Список, сколько как дерево (то же самое в конце), где каждый Узел был бы контейнером для других записей Дерева... Но тогда вам нужно развернуть чтобы получить доступ к самому элементу, и его использование звучит немного сложнее. Кроме того, я должен признать, что достижение только родительского уровня, как я предложил изначально в своем посте, прекрасно подходит для требований, но не на 100% идеально, и просто ради того, чтобы знать, что я хотел бы найти лучшее решение без необходимости любую дополнительную обертку. - person lqbweb; 28.07.2016