Можно ли поставить верхнюю границу типа, обязывающую класс иметь конкретное определение типа члена? - PullRequest
0 голосов
/ 16 марта 2020

Давайте рассмотрим:

 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. Должен ли я написать макрос для этого?

1 Ответ

1 голос
/ 16 марта 2020

Я не совсем уверен, почему вы говорите, что использование зависимого от пути типа будет раздражать, поэтому, возможно, мой ответ будет полностью отключен, извините за это.

В чем проблема?

class Generic[M <: Abstract](val m: M) {
  def get: M#T = m.get
  def set(t: m.T): Unit = m.set(t)
}

object Generic {
  def like[N <: Abstract](n: N): Generic[N] = new Generic(n)
  def trans[N <: Abstract](n :N): N#T = like(n).get
}

class Foo
val f = new Generic(new Concrete(new Foo))
f.set(new Foo)

Вы говорите, что у вас не всегда будет экземпляр m: M, но можете ли вы привести реальный пример того, чего именно вы хотите достичь?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...