Понимание пределов поддержки Scala GADT

ошибка в тесте.тест кажется неоправданным:

sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]

case class Test[U]() { 
  def test[V](t: A[Option[U], V]) = t match {
    case B() => null // constructor cannot be instantiated to expected type; found : B[V] required: A[Option[U],?V1] where type ?V1 <: V (this is a GADT skolem)
  }
  def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}

object Test2 {
  def test2[U, V](t: A[Option[U], V]) = t match {
    case B() => null // This works
  }
}

есть несколько способов изменить ошибку или уйти:

Если мы удалим параметр V на черте A (и case class B), часть ошибки "GADT-skolem" исчезнет, но часть "конструктор не может быть создан" остается.

если мы переместим параметр U тестового класса В тест.метод теста, ошибка уходит. Почему ? (Аналогично, ошибка отсутствует в Условие_2.условие_2)

следующая ссылка также идентифицирует эту проблему, но я не понимаю предоставленного объяснения. http://lambdalog.seanseefried.com/tags/GADTs.html

это ошибка в компиляторе ? (2.10.2-RC2) для

Спасибо за любую помощь в разъяснении этого.


2014/08/05: мне удалось еще больше упростить код и привести еще один пример, когда U привязан вне непосредственной функции без вызывает ошибку компиляции. Я все еще наблюдаю эту ошибку в 2.11.2.

sealed trait A[U]
case class B() extends A[Unit]

case class Test[U]() {
  def test(t: A[U]) = t match {
    case B() => ??? // constructor cannot be instantiated to expected type; found : B required: A[U]
  }
}

object Test2 {
  def test2[U](t: A[U]) = t match {
    case B() => ??? // This works
  }
  def test3[U] = {
    def test(t: A[U]) = t match {
      case B() => ??? // This works
    }
  }
}

упрощенный, как это выглядит больше похоже на ошибку компилятора или ограничение. Или я что-то упускаю ?

5 ответов


шаблоны конструкторов должны соответствовать ожидаемому типу шаблона, что означает B <:>

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

// Instead of this
def test1(t: A[U]) = t match { case B() => ??? }

// Write this
def test2(t: A[U]) = (t: A[_]) match { case B() => ??? }

добавление: в комментарии спрашивающий говорит: "вопрос прост, почему он не работает с параметром типа на уровне класса, но работает с на уровне метода."Параметры типа экземпляра класса видны извне и имеют неопределенное время жизни, ни одно из которых не верно для параметров типа экземпляра метода. Это имеет последствия для типа soundness. Учитывая Foo[A], как только я владею Foo[Int] , тогда A должен быть Int при обращении к этот случай, навсегда и навсегда.

в принципе вы можете обрабатывать параметры типа класса аналогично внутри вызова конструктора, потому что параметр типа по-прежнему несвязан и может быть выведен оппортунистически. Вот и все. Как только он появится в мире, нет места для переговоров.

еще одно добавление: я вижу, что люди делают это много, принимая за предпосылку, что компилятор является образцом правильности, и все, что нам осталось сделать, это обдумать его вывод пока наше понимание не разовьется настолько, чтобы соответствовать ему. Такая умственная гимнастика имела место под этой палаткой, мы могли бы персонал Cirque de Soleil пару раз.

scala> case class Foo[A](f: A => A)
defined class Foo

scala> def fail(foo: Any, x: Any) = foo match { case Foo(f) => f(x) }
fail: (foo: Any, x: Any)Any

scala> fail(Foo[String](x => x), 5)
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  at $anonfun.apply(<console>:15)
  at .fail(<console>:13)
  ... 33 elided

это текущая версия scala-это все еще то, что она делает. Никакое предупреждение. Поэтому, возможно, спросите себя, разумно ли предлагать презумпцию правильности языку и реализации, которые так тривиально необоснованны после более чем десяти лет существования.


похоже, это предупреждение компилятора. От этой, Мартин Одерский говорит так:

In the method case, what you have here is a GADT: 
Patterns determine the type parameters of an corresponding methods in the scope of a pattern case.     
GADTs are not available for class parameters.    

As far as I know, nobody has yet explored this combination, and it looks like it would be quite tricky to get this right.

PS: спасибо @retronym кто предоставил ссылку из обсуждения здесь


О том, почему он выдает ошибку в случае класса:

это работает:

sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]
case class Test[U]() {

   def test[V, X <: Unit](t: A[Option[X], V]) = t match {
     case B() => null
   }
   def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}

object Test2 {
      def test2[U, V](t: A[Option[U], V]) = t match {
         case B() => null // This works
       }
 }

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

scala> :paste
// Entering paste mode (ctrl-D to finish)

abstract class Exp[A]{
        def eval:A = this match {
            case Temp(i) => i     //line-a
          }
        }
case class Temp[A](i:A) extends Exp[A]

// Exiting paste mode, now interpreting.

чтобы понять это, от §8.1.6: выше Temp - это полиморфный тип. Если класс case является полиморфным, то:

если класс case является полиморфным, то его параметры типа инстанцировать так что инстанцирование с соответствует ожидаемому тип узора. Экземпляры формальных типов параметров c первичный конструктор затем берутся в качестве ожидаемых типов шаблоны компонентов p 1,. . . П. Н . Шаблон соответствует всем объектам создано из вызовы конструктора c (v1,. . . , в Н ), где каждая шаблон элемента p i соответствует соответствующему значению v i .

т. е. компилятор умно пытается создать экземпляр Temp in line-A такой, что он соответствует основному конструктору this( который, если выше скомпилирован успешно, то в случае Temp(1) было бы что-то вроде Exp[Int] и, следовательно, компилятор создает экземпляр Temp в строке-a с параметром as Int.


Теперь в нашем случае: компилятор пытается создать экземпляр B. Он видит, что t типа A[Option[U],V] здесь U уже исправлено и получено из параметра класса и V является общим типом метода. При попытке инициализации B он пытается создать таким образом, что он в конечном счете получает A[Option[U],V]. Так и с B() он как-то пытается получить A[Option[U],V]. Но он не может как B is A[Option[Unit],V]. Следовательно, он в конечном счете не может инициализировать B. Фиксируя это делает работа


Его не требуется в случае test-2, потому что: компилятор, как описано выше, пытается инициализировать параметр типа B. Он знает, что t имеет параметр типа [Option[U], V], где U и V являются общим методом wrt и получены из аргумента. Он пытается инициализировать B на основе agrument. Если аргумент был новым B[String], он пытается вывести B[String] и, следовательно, U автоматически получается как опция[Unit]. Если аргумент был новым A[Option[Int], String] тогда это явно не совпадет.

разница связана с тем, какую информацию компилятор и среда выполнения имеют в каждом случае в сочетании с ограничениями на типы. Ниже неоднозначность проясняется тем, что U является параметром признака и класса,А X-параметром типа метода.

sealed trait A[U]
case class B(u: Unit) extends A[Unit]

class Test[U]() {
  def test(t: A[U]) = t match {
    case B(u) => u // constructor cannot be instantiated to expected type; found : B required: A[U]
  }
}

object Test2 {
  def test2[X](t: A[X]) = t match {
    case B(x) => x // This works
  }
  def test3[X] = {
    def test(t: A[X]) = t match {
      case B(x) => x // This works
    }
  }
}

Test2.test2(new B(println("yo")))

В Условие_2.test2, компилятор знает, что экземпляр A[X] будет предоставлен без ограничений на X. Он может генерировать код, который проверяет предоставленный параметр, чтобы увидеть, является ли он B, и обрабатывать случай.

в тесте метода тестирования класса компилятор не знает, что некоторые[X] предоставляются при вызове, но что некоторые конкретные тип, U, представлен. Этот тип U может быть что-нибудь. Тем не менее, шаблон соответствует алгебраическому типу данных. Этот тип данных имеет ровно одну допустимую вариацию типа a[Unit]. Но эта подпись для[U], где U не ограничена единицей do. Это противоречие. Определение класса говорит, что U-параметр свободного типа, метод говорит, что это единица.

представьте, что вы создаете тест:

val t = new Test[Int]()

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

def test(t: A[Int])

нет такого типа A[Int]. Сопоставление шаблонов на B-это когда компилятор проверяет это условие. A[U] для любого U несовместим с типом, поэтому " конструктор не может быть создан для ожидаемого типа; найдено : B требуется: A[U]"

разница с параметром типа метода и класса заключается в том, что он связан, а один из них свободен, в то время как метод вызывается во время выполнения.

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

def test(t: A[U])

подразумевает, что U является единицей, что несовместимо с объявлением класса, что U-это что угодно.


редактировать: это не ответ на вопрос. Я держу его для справки (и комментариев).


ссылка, которую вы предоставили, уже дает ответ.

в случае параметризованного метода,U выводится из аргумента фактического вызова метода. Итак, дело в том, что означает, что U >: Unit (в противном случае метод не мог быть вызван с помощью B) и компилятор счастливый.

в случае параметризованного класса, U не зависит от аргумента метода. Итак, дело в том, чтоB() был выбран, ничего не говорит компилятору о U и он не может подтвердить, что U >: Unit. Я думаю, если вы добавите такой тип, связанный с U Он должен работать. Но я не пробовал.


компилятор абсолютно корректен со своим сообщением об ошибке. Простой пример должен хорошо описать проблему:

sealed trait A[U]
class B extends A[Unit]

class T[U] {
  val b: A[Unit] = new B
  f(b) // invalid, the compiler can't know which types are valid for `U`
  g(b) // valid, the compiler can choose an arbitrary type for `V`

  def f(t: A[U]) = g(t)
  def g[V](t: A[V]) = ???
}

f и g имеют подписи разных типов. g принимает A[V] здесь V - параметр произвольного типа. С другой стороны,--2--> принимает A[U] здесь U не является произвольным, но зависит от внешнего класса T.

компилятор не знает какого типа U может быть в тот момент, когда он typechecks T - он должен typecheck экземпляр T чтобы узнать, какие типы используются. Можно сказать, что U конкретный тип внутри T, но общий тип вне его. Это означает, что он должен отклонить каждый фрагмент кода, который попадает внутрь T более конкретно о типе U.

вызов g внутри f явно разрешено-ведь произвольный тип для V можно выбрать, который U в этом случае. Потому что U больше никогда не упоминается в g не имеет значения, какой тип он может иметь.

ваш другой пример кода лежит в основе тех же ограничений - это просто более сложный пример.

кстати, что это за код

def test3[U] = {
  def test(t: A[U]) = t match {
    case B() => ??? // This works
  }
}

действительно выглядит странно для меня. Не имеет значения, если U связан классом или методом-изнутри области привязки мы не можем дать никаких гарантий относительно его типа, поэтому компилятор должен отклонить это образец тоже совпадает.