Функция ограничения, основанная на происхождении (тип, зависящий от пути? Генерация типа?)

Извините за ужасный заголовок, но не уверен в лучшем. Вот грубое упрощение моей проблемы (извините, если это кажется настолько тривиальным, что это бессмысленно):

class RList[T](data: List[T]) {
   def map[V](f: T=>V): RList[V] = ...
}

Идея RList (Ограниченный список) заключается в том, что вы не можете изменить его размер или изменить порядок элементов в нем. Но вы можете использовать функции, которые дают вам новый RList с измененными данными в нем.

Теперь должна быть функция, которая создает RLists. Это может иметь подпись что-то вроде:

def toRList[T](values: List[T]): RList[T] = ...

Все идет нормально. Но теперь сложная часть. Мне нужна функция, которая работает так:

def zip[T, V](left: RList[T], right: RList[V]): RList[(T,V)]

Но с дополнительным ограничением, которое left а также right имеют одинаковое происхождение. Таким образом, они гарантированно будут одинакового размера.

например код, который должен компилироваться:

val x = toRList(List(1, 2, 3))
val y = x.map(_ * 2)
val z = y.map(_.toString)
zip(y,z)

например, код, который не должен компилироваться

val y = toRList(List(2, 4, 6))
val z = toRList(List("one", "two"))
zip(y,z)

* Примечание: в моей первоначальной задаче ограничение на zip должно быть таким, чтобы они были из одного и того же "источника". Просто гарантировать, что они имеют одинаковую длину, недостаточно (не говоря уже о том, что размер списка неизвестен во время компиляции) *

Я также должен иметь возможность использовать zip несколько раз, так что-то вроде этого должно скомпилировать

zip(a,zip(b,c))

(при условии, a, b а также c из того же источника)

Спасибо!

2 ответа

Решение

Одним из недостатков изготовления RList внутренняя черта продюсера состоит в том, что становится менее приятно писать методы или функции с RList аргументы за пределами продюсера - у вас много всего этого:

def foo[P <: RListProducer, T](rl: P#RList[T]) = ???

Альтернативой является дать RList член типа, который будет уникальным для каждого "источника" RListНо каждый источник передаст своим "детям". Что-то вроде этого:

trait RList[T] { outer =>
  type S
  protected val wrapped: List[T]

  def map[V](f: T => V) = new RList[V] {
    type S = outer.S
    protected val wrapped = outer.wrapped.map(f)
  }

  def zip[V](r: RList[V] { type S = outer.S }) = new RList[(T, V)] {
    type S = outer.S
    protected val wrapped = outer.wrapped.zip(r.wrapped)
  }
}

object RList {
  def toRList[T](ts: List[T]) = new RList[T] {
    type S = this.type
    protected val wrapped = ts
  }
}

Теперь предположим, что у нас есть следующее:

val a = RList.toRList(1 :: 2 :: 3 :: Nil)
val b = a.map(_.toString)
val c = RList.toRList("1" :: "2" :: "3" :: Nil)

Сейчас a zip b (или же a zip b zip a zip aи т. д.) будет компилироваться, но если вы добавите c вы получите ошибку компилятора.


Примечание: я изначально написал zip следующее:

def zip[V](r: RList[V])(implicit ev: r.S =:= S) = new RList[(T, V)] { ... }

Это дает немного более приятное сообщение об ошибке компилятора, но если вы работаете до 2.10, это требует включения зависимых типов методов с -Ydependent-method-types,

Это работает для вас?

object PathDependentTypes {
  trait RListProducer {
    trait RList[T] {
      def map[V](f: T => V): RList[V]
      def zip[V](r: RList[V]) : RList[(T, V)]
    }
    def getList[T]: RList[T] = ???
  }

  class A extends RListProducer

  def main(args: Array[String]) {
    val aSource = new A
    val a = aSource.getList[Int]
    val anotherSource = new A
    val b = anotherSource.getList[String]

    val m1 = a.map( _ * 2)
    val m2 = a.map( _.toString)

    val z1 = m1.zip(m2)
    //val z2 = a.zip(b) //doesn't compile because A and B aren't the same.
    val z3 : A#RList[(Int, (Int, String))] = a zip (m1 zip m2)
  }
}
Другие вопросы по тегам