Зависимый тип Scala не компилируется

Этот код должен компилироваться на Scala:

trait Pipe {
  type Input
  type Output
  def apply(input: Input): Output
}

object Pipe {
  trait Start extends Pipe {
    override type Input = Seq[String]
  }

  abstract class Connect(val prev: Pipe) extends Pipe {
    override type Input = prev.Output
  }
}

object Pipe1 extends Pipe.Start {
  override type Output = Int
  override def apply(input: Input): Output = 
   input.length
}

object Pipe2 extends Pipe.Connect(prev = Pipe1) {
  override type Output = Boolean
  override def apply(input: Input): Output = 
   input%2 == 0
}

Pipe1 компилируется нормально, но Pipe2 не компилируется с:

value % is not a member of Pipe2.this.Input
     input%2 == 0
          ^

Я знаю, что могу решить эту проблему с помощью дженериков вместо зависимых типов, но это должно работать, поскольку Pipe2.Input должен проверять тип на Int из Pipe1.Output


person pathikrit    schedule 02.04.2019    source источник


Ответы (2)


Элемент prev = Pipe в вызове конструктора не является правильным путем, компилятор не может связать с ним какую-либо информацию о типе, поэтому вы получаете довольно бесполезный prev.Output =:= Input для некоторого неопределенного prev: Pipe, для которого установлено значение something в конструкторе.

С минимальным изменением работает как положено:

trait Pipe {
  type Input
  type Output
  def apply(input: Input): Output
}

object Pipe {
  trait Start extends Pipe {
    override type Input = Seq[String]
  }

  abstract class Connect extends Pipe {
    val prev: Pipe
    override type Input = prev.Output
  }

}

object Pipe1 extends Pipe.Start {
  override type Output = Int
  override def apply(input: Input): Output = 
    input.length
}

object Pipe2 extends Pipe.Connect {
  val prev = Pipe1
  override type Output = Boolean
  override def apply(input: Input): Output = input % 2 == 0
}

Вот почему он называется зависимым от пути (не зависимым от члена, не зависимым от значения и т. Д.).

person Andrey Tyukin    schedule 03.04.2019
comment
Это работает ... но не должен ли компилятор знать? Аргументом Connect не может быть просто какая-то труба, а конкретная труба с определенным типом вывода? - person pathikrit; 04.04.2019
comment
@pathikrit Компилятор видит это так: 1) Есть переменная-член prev: Pipe. 2) Есть определение типа type Input = prev.Output. 3) Есть конструктор, который устанавливает prev на некоторое Pipe. Вот и все. Конструктор мог делать все, что угодно. Он может игнорировать аргумент и случайным образом установить prev на какой-то другой Pipe. Он может хешировать аргумент и передавать хеш в нейронную сеть, которая конфабулирует другой Pipe экземпляр. Нет ничего, что связывало бы список аргументов конструктора с переменной-членом. У них просто общее имя prev, не более того. - person Andrey Tyukin; 04.04.2019
comment
Должна ли система типов языка X сделать шаг Y к более сложному варианту Z полностью зависимой теории типов - я не знаю, я бы предпочел воздержаться от таких утверждений, как разработчики языка X должны реализовать Y, потому что такие операторы, как правило, гораздо проще сделать, чем реализовать их на практике. Можно ли себе представить, что существует более сложная система типов, которая заметит это? Да, это можно вообразить. Стоит ли Scala-людям попытаться реализовать это? Я не знаю. - person Andrey Tyukin; 04.04.2019

@ Ответ Андрея-Тюкина работает выше. Я также нашел эту работу:

trait Pipe {
  type Input
  type Output
  def apply(input: Input): Output
}

object Pipe {
  trait Start extends Pipe {
    override type Input = Seq[String]
  }

  abstract class Connect[O](val prev: Pipe.Emitting[O]) extends Pipe {
    override type Input = O
  }

  type Emitting[O] = Pipe {type Output = O}
}

object Pipe1 extends Pipe.Start {
  override type Output = Int
  override def apply(input: Input): Output = 
   input.length
}

object Pipe2 extends Pipe.Connect(prev = Pipe1) {
  override type Output = Boolean
  override def apply(input: Input): Output = 
   input%2 == 0
}
person pathikrit    schedule 03.04.2019
comment
Этот O-тип больше не зависит от каких-либо путей: вы используете вариант Aux-pattern, чтобы переместить член типа Output в общий тип, а затем вы используете общий тип, чтобы установить для него Input. Я думаю, что стоит попытаться избавиться от членов type Input и type Output и просто выставить тип ввода / вывода как общие типы. Если тип относится к отдельному компоненту, превратите его в член типа; Если это описание интерфейса для соединения нескольких компонентов, сделайте его универсальным, доступным извне. - person Andrey Tyukin; 04.04.2019