Вернуть объект, привязанный к классу типов из метода

У меня есть класс типов для Path. Он содержит метод увеличения (путь увеличивается путем перемещения по вершинам), который должен иметь возможность возвращать любой тип, который снова соответствует классу типов Path. Я подумал, что-то вроде этого должно работать (элементы данных удалены для простоты):

case class Circle()
case class Polyline()
trait Path[T] {
  /// Note how the return type T2 should again have a Path[T2] typeclass
  def enlarge[T2 : Path](path : T) : T2
}

object Path {
  implicit object PathCircle extends Path[Circle] {
    def enlarge[T2 : Path](path : Circle) = Polyline()
  }
  implicit object PathPolyline extends Path[Polyline] {
    def enlarge[T2 : Path](path : Polyline) = Polyline()
  }
}

object Test {
  import Path._

  implicitly[Path[Circle]].enlarge(Circle())
}

Однако я получаю следующую ошибку:

[error] test.scala:12: type mismatch;
[error]  found   : Polyline
[error]  required: T2
[error]  Note: implicit object PathPolyline is not applicable here because it comes after the application point and it lacks an explicit result type
[error]     def enlarge[T2 : Path](path : Circle) = Polyline()
[error]                                                     ^
[error] test.scala:15: type mismatch;
[error]  found   : Polyline
[error]  required: T2
[error]     def enlarge[T2 : Path](path : Polyline) = Polyline()
[error]                                                       ^
[error] test.scala:22: ambiguous implicit values:
[error]  both object PathPolyline in object Path of type Path.PathPolyline.type
[error]  and object PathCircle in object Path of type Path.PathCircle.type
[error]  match expected type Path[T2]
[error]   implicitly[Path[Circle]].enlarge(Circle())
[error]                                   ^
[error] three errors found
[error] (compile:compile) Compilation failed

Что я делаю неправильно?

2 ответа

Поэтому после долгих проб и ошибок я нашел решение, которое, похоже, работает. По сути, можно вернуть объект PathBox, который является объектом-оболочкой как для возвращенного экземпляра Path, так и для его неявного свидетельства класса типов Path. Соедините это с реализациями фактического класса типов для PathBox, и вы получите возвращаемый параметр, для которого снова реализован класс типов. Единственная проблема, с которой я столкнулся при решении, заключается в том, что отдельные реализации классов типов не могут иметь циклических зависимостей, но я могу с этим смириться, пока. Код:

case class Circle()
case class CircleArc()
case class Polyline()

/// A container storing both an object adhering to an EnlargablePath
/// typeclass, plus the instance of the typeclass itself
case class PathBox[T](val t : T)(implicit val tInst : EnlargablePath[T])

/// Any path that can be enlarged and potentially return a new kind of
/// path, which still adheres to the EnlargablePath typeclass however
trait EnlargablePath[T] {
  def enlarge[_](path : T) : PathBox[_]
}

object EnlargablePath  {

  // A Polyline can always return a larger polyline
  implicit object PathPolyline extends EnlargablePath[Polyline] {
    def enlarge[_](path : Polyline) = PathBox(Polyline())
  }

  // Enlarging a circle only req. a new radius, so it returns a circle
  implicit object PathCircle extends EnlargablePath[Circle] {
    def enlarge[_](path : Circle) = PathBox(Circle())
  }

  // Enlarging a CircleArc, results in a full circle. This actually
  // requires the PathCircle object to be defined before!
  implicit object PathCircleArc extends EnlargablePath[CircleArc] {
    def enlarge[_](path : CircleArc) = PathBox(Circle())
  }

  // Make sure that a PathBox[_] can also be treated as an EnlargablePath
  implicit def PathPathBox[T] = new EnlargablePath[PathBox[T]] {
    def enlarge[_](pathBox : PathBox[T]) = pathBox.tInst.enlarge(pathBox.t)
  }

  // For nicer syntax, any object that implements the EnlargablePath
  // typeclass gets its methods added. If you care about performance,
  // take a look at spire's (the scala math library) macros for this
  // purpose
  implicit class EnlargablePathOps[T : EnlargablePath](val p : T) {
    def enlarge = implicitly[EnlargablePath[T]].enlarge(p)
  }
}

object Test {
  import EnlargablePath._

  println(Circle().enlarge)
  println(Circle().enlarge.enlarge)
  println(Circle().enlarge.enlarge.enlarge)
}

Это основано на этом разговоре о программировании на уровне типов (стоит посмотреть)

Академический на английский: система типов Scala, зависимые типы и что это значит для вас

case class Circle()
case class Polyline()

// This is our type class.
// More type parameters could be added if more functions were added.
trait Path[T,E]{
  def enlarge(p:T):E
}

object Path {
  implicit object PathCircle extends Path[Circle,Polyline] {
    override def enlarge(path : Circle): Polyline = Polyline()
  }
  implicit object PathPolyline extends Path[Polyline,PolyLine] {
    override def enlarge(path : Polyline) = Polyline()
  }
  def enlarge[T,E](path:T)(implicit tp:Path[T,E]):E = tp.enlarge(path)
}

object Test {
  import Path._

  // The easist way to call it
  val pl: Polyline = Path.enlarge(Circle())

  // Or since we're already importing Path
  val pl2: Polyline = enlarge(Circle())
}

Преимущество этого подхода в том, что наш тип возврата может быть очень конкретным. Это может быть только хорошо!


Сначала я пытался заставить его работать с членами типа:

trait Path[T] {
  type E 
  def enlarge(path : T) : E
}
...
implicit object PathCircle extends Path[Circle] {
  type E = Circle
  ...

Но это только дало тип возврата.

val pl:Path[Circle]#E = enlarge(Circle())
Другие вопросы по тегам