#scala #dependent-type #path-dependent-type
#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
Ответ №1:
prev = Pipe
Дело в том, что при вызове конструктора указан неправильный путь, компилятор не может привязать к нему какую-либо информацию о типе, поэтому в итоге вы получаете довольно бесполезный, prev.Output =:= Input
для некоторого неопределенного prev: Pipe
значения, для чего-то установлено значение в конструкторе.
С минимальными изменениями он работает так, как ожидалось:
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
}
Вот почему он называется зависимым от пути (не зависит от элемента, не зависит от значения и т.д.).
Комментарии:
1. Это работает… но разве компилятор не должен знать? Аргументом
Connect
не может быть просто «некоторый канал`, а конкретный канал с определенным типом вывода?2. @pathikrit Компилятор видит это следующим образом: 1) Существует переменная-член
prev: Pipe
. 2) Существует определение типаtype Input = prev.Output
. 3) Существует конструктор, который устанавливаетprev
значение somePipe
. Вот и все. Конструктор может делать что угодно. Он может проигнорировать аргумент и установитьprev
какое-то другоеPipe
значение случайным образом. Он мог бы хэшировать аргумент и передавать хэш в нейронную сеть, которая конфабулирует другойPipe
экземпляр. Нет ничего, что связывало бы список аргументов конструктора с переменной-членом. Они просто разделяют имяprev
, не более того.3. Должна ли система типов языка X сделать шаг Y к какому-то более сложному варианту Z теории полностью зависимых типов — я не знаю, я бы предпочел воздержаться от заявлений типа «разработчики языка X должны реализовать Y», потому что такие заявления, как правило, намного проще сделать, чем выполнить фактическую реализацию. Можно ли представить, что существует более сложная система типов, которая заметила бы это? Да, это можно себе представить. Должны ли разработчики Scala попытаться реализовать это? Я не знаю.
Ответ №2:
Ответ @Andrey-Tyukin работает выше. Я также нашел эту работу вокруг:
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
}
Комментарии:
1. Этот
O
тип больше не зависит ни от каких путей: вы используете вариантAux
-pattern для перемещения элемента типаOutput
в универсальный тип, а затем используете универсальный тип для его установкиInput
. Я думаю, что стоило бы попытаться избавиться от элементовtype Input
иtype Output
и просто представить тип ввода / вывода как универсальные типы. Если тип относится к одному компоненту, переведите его в тип member; Если это «описание интерфейса» для подключения нескольких компонентов, сделайте его универсальным, к которому легко получить доступ извне.