Функция ограничения, основанная на происхождении (тип, зависящий от пути? Генерация типа?)
Извините за ужасный заголовок, но не уверен в лучшем. Вот грубое упрощение моей проблемы (извините, если это кажется настолько тривиальным, что это бессмысленно):
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)
}
}