Что означает «не соответствует границам параметров типа» в scala? - PullRequest
1 голос
/ 13 марта 2019

У меня есть следующая простая программа, которая определяет 2 одинаковые верхние границы для параметра типа и псевдонима абстрактного типа соответственно:

package scala.spike.typeBoundInference

object Example1 {
  trait Domain {
  }

  trait Impl {

    type DD <: Domain
    type GG <: StaticGraph[DD]

    type A1
    type A2
    type A3
    // ... this type list can go very long
    // so inlining them as generic type parameters is impossible


    final type Builder = StaticGraph.Builder[DD, GG]
  }

  trait DSL[I <: Impl] {

    val impl: StaticGraph.Builder[I#DD, I#GG]
  }

  trait StaticGraph[T <: Domain] {}

  object StaticGraph {

    trait Builder[D <: Domain, G <: StaticGraph[D]] {}

  }
}

Однако, scala отказывается его компилировать:

Ошибка: (16, 27) аргументы типа [I # DD, I # GG] не соответствуют признаку Границы параметра типа строителя [D <: scala.spike.typeBoundInference.Example1.Domain, G <: scala.spike.typeBoundInference.Example1.StaticGraph [D]] val impl: StaticGraph.Builder [I # DD, I # GG] </p>

Что здесь может пойти не так?

  • DD <: проверка домена </p>

  • GG <: проверка StaticGraph [DD] </p>

нет никаких оснований считать, что это небезопасно.

Тем временем я обнаружил, что если класс StaticGraph [T] объявлен как ковариантный компилятор scala, он будет работать успешно. Это еще хуже (по какой-то причине StaticGraph [T] должен быть инвариантным), так как привязка типа GG <: StaticGraph [DD] означает, что если тип DD определен, то GG является подклассом StaticGraph [DD], но не является необходимым подкласс StaticGraph [Domain], что именно здесь я и хочу. </p>

ОБНОВЛЕНИЕ 1 : я прочитал все ответы и комментарии и каким-то образом у меня сложилось впечатление, что основная причина в том, что нет никакой гарантии, что для любого экземпляра i из Impl, тип обязательна только гарантия того типа

i.DD <:< Impl#DD и Imp#GG <:< StaticGraph[Impl#DD]

но не StaticGraph[i.DD] <:< StaticGraph[Impl#GG]

, таким образом, i.GG <:< StaticGraph[i.DD] также не гарантируется.

Тем не менее, я провёл быстрый эксперимент, чтобы проверить эту идею, которая оказывается неверной:

object Example1 {

  trait Domain {}
  class D1 extends Domain {}

  trait Impl {

    type DD <: Domain
    type GG <: StaticGraph[DD]
  }

  class StaticGraph[T <: Domain] {}

  object Impl1 extends Impl {

    type DD = D1
    type GG = StaticGraph[Domain]
  }

  //or this:

  val impl = new Impl {

    type DD = D1
    type GG = StaticGraph[Domain]
  }
}

В этом случае компилятор выдает ошибку:

Ошибка: (19, 10) переопределение типа GG в признаке Impl с границами <: scala.spike.TypeBoundInference.Example1.StaticGraph [scala.spike.TypeBoundInference.Example1.Impl1.DD]; тип GG имеет несовместимый тип тип GG = StaticGraph [домен] </p>

Если вы считаете, что ограничение типа не выполняется в некоторых случаях, не могли бы вы привести контрпример?

UPDATE2 : выясняется, что согласно ответу это правда:

i.GG <:< StaticGraph[i.DD]

но это может быть ложным:

Impl#GG <:< StaticGraph[Impl#GG].

, поэтому в контексте DSL это также может быть ложным:

I#GG <:< StaticGraph[I#GG] (3)

Но это только часть головоломки, чтобы доказать, что это небезопасный тип, мы должны построить контрпример для DSL [I], который делает недействительным условие (3). Таким образом, остается старый вопрос: можно ли построить контрпример?

Ответы [ 2 ]

2 голосов
/ 13 марта 2019

Что здесь может пойти не так?

GG <: StaticGraph [DD] check </p>

Объявляя type GG <: StaticGraph[DD], вы устанавливаете связь между типом члена (это то же самое, что и <: StaticGraph[this.DD]). Это означает, что вам нужно рассмотреть экземпляры Impl.

Для любого val i: Impl у вас есть i.DD <: Domain и i.GG <: StaticGraph[i.DD]. У вас также есть i.DD <: I#DD. Но у вас нет есть i.DD =:= I#DD! Так что StaticGraph[i.DD] и StaticGraph[I#DD] не связаны (для инварианта StaticGraph). Так же как и i.GG (или I#GG) и StaticGraph[I#DD].

Для компиляции вам нужно, чтобы все i.DD были одинаковыми (что также гарантирует i.DD =:= I#DD). И есть способ сделать это:

trait DSL[T <: Domain, I <: Impl { type DD = T } ] 

сделает код скомпилированным (без каких-либо других изменений).

Если StaticGraph является ковариантным, отношения получаются:

I#GG =:= (kind of)
i.GG forSome { val i: I } <:
StaticGraph[i.DD] forSome { val i: I } <:
StaticGraph[I#DD] forSome { val i: I } =:=
StaticGraph[I#DD]
0 голосов
/ 14 марта 2019

ОК. Проблема решена:

import scala.language.higherKinds

object Example5 {

  trait Domain {}
  trait D1 extends Domain

  trait Impl {

    type DD <: Domain
    type GG[T <: Domain] <: StaticGraph[T]
  }

  trait DSL[I <: Impl] {

    val impl: Builder[I#DD, I#GG]
  }

  trait StaticGraph[T <: Domain] {}

  trait Builder[D <: Domain, G[T <: Domain] <: StaticGraph[T]] {}
}

Не могу поверить, что мне нужно использовать более высокий вид для такого банального вопроса: - <</p>

Почему он компилируется?Это разъединяет ограничение типа и задерживает его, пока это не станет необходимым.(это единственное объяснение, которое я могу придумать)

...