Давайте рассмотрим:
trait Abstract {
type T
def get :T
def set(t :T) :Unit
}
class Concrete[X](var x :X) extends Abstract {
override type T = X
override def get :T = x
override def set(t :T) = x = t
}
class Generic[M <: Abstract](val m :M) {
def get :M#T = m.get
def set(t :M#T) :Unit = m.set(t)
def like[N <: Abstract](n :N) :Generic[N] = new Generic(n)
def trans[N <: Abstract](n :N) :N#T = like[N](n).get
}
Класс Generic
по праву не будет компилироваться здесь, потому что M # T может быть буквально любым существующим типом, а не mT. Это можно «исправить» двумя способами:
def set(t :m.T) :Unit = ???
или
class Generic[M <: Abstract with Singleton]
Первое невозможно, поскольку на практике Generic
может даже не иметь экземпляра M
для использования его типа члена (что происходит в моей реальной жизни). case), и передача зависимых от пути типов совместимым образом между классами и методами практически невозможна. Вот один из примеров того, почему зависимые от пути типы слишком сильны (и вызывают огромные неудобства):
val c = new Concrete[Int](0)
val g = new Generic[c.type](c)
c.set(g.get)
Последняя строка в приведенном выше примере не компилируется, даже если g :Generic[c.type]
и, следовательно, get :c.type#T
который упрощается до c.T
.
. Второй вариант несколько лучше, но все равно требуется, чтобы у каждого класса с параметром типа M <: Abstract
был экземпляр M
при создании и захвате m.T
в качестве типа. параметр и передача везде привязанного типа Abstract { type T = Captured }
по-прежнему очень трудны.
Я бы хотел поставить ограниченный тип на T
, указав, что только подклассы Abstract
имеют конкретное определение T
действительны, что составит m.T =:= M#T
за каждый m :M
и аккуратно все решит. До сих пор я не видел никакого способа сделать это. Я попытался поставить ограничение типа:
class Generic[M <: Abstract { type T = O } forSome { type O }]
, которое, казалось бы, решило проблему set
, но не удалось с trans
:
def like[N <: Abstract { type T = O } forSome { type O }](n :N) :Generic[N] = ???
def trans[N <: Abstract { type T = O } forSome { type O}](n :N) :N#T = ???
, который мне кажется чистым типом системный недостаток. Кроме того, в некоторых случаях это на самом деле ухудшает ситуацию, поскольку определяет T
уже как some O(in class Generic)
и не объединяет его при появлении дополнительных ограничений:
def narrow[N <: M { type T = Int }](n :N) :M#T = n.get
Приведенный выше метод будет скомпилирован как часть класса Generic[M <: Abstract]
, но не Generic[M <: Abstract { type T = O } forSome { type O}]
. Замена Abstract { type T = O } forSome { type O }
на Concrete[_]
дает очень похожие результаты. Оба этих класса можно исправить, введя параметр типа для T
:
class Generic[M <: Abstract { type T = O }, O]
class Generic[M <: Concrete[_]]
К сожалению, в некоторых местах я использую конструкторы типов с двумя аргументами, такие как
trait To[-X <: Abstract, +Y <: Abstract]
и использовать инфиксную нотацию X To Y
как часть моего dsl, поэтому изменение их на много аргументы, стандартные обобщенные c классы не вариант.
Есть ли хитрость, чтобы обойти эту проблему, а именно сузить допустимые параметры типа класса так, чтобы их тип члена (или параметр типа, мне все равно) были выражаемы как типы и совместимы друг с другом ?. Думайте о M
как о некой фабрике значений T
и о Generic
как о своих входных данных. Я хотел бы параметризовать Generic
таким образом, чтобы его экземпляры были предназначены для конкретной реализации классов из M
. Должен ли я написать макрос для этого?