Итак, это может звучать как общий вопрос о дизайне языка, но я думаю, что здесь есть что-то конкретное. В частности, меня интересуют, какие технические проблемы препятствуют тому, что код kludgy следует из общего полезного.
Мы все знаем, что "вывод типа Scala не так хорош, как Haskell", и что есть много причин, по которым он просто не может быть таким же хорошим, и все еще выполняет все, что делает Scala. Но то, что также становится очевидным, после программирования Scala достаточно долго, заключается в том, что неверный вывод типа на самом деле не так уж плох, так же как и многословие, требуемое для указания некоторых общих типов. Так, например, в полиморфной функции tail
,
def tail[A](ls: List[A]) =
ls match {
case Nil => sys.error("Empty list")
case x :: xs => xs
}
требуется явно указать параметр типа, чтобы сделать метод полезным; никоим образом не вокруг. tail(ls: List[Any])
не будет работать, потому что Scala не может понять, что тип результата совпадает с типом ввода, хотя для человека это "очевидно".
Итак, вдохновленный этой трудностью и зная, что Scala иногда может быть более умным с членами типа, чем с параметрами типа, я написал версию List
, которая использует члены типа:
sealed trait TMList {
self =>
type Of
def :::(x: Of) = new TMCons {
type Of = self.Of
val head = x
val tail = (self: TMList { type Of = self.Of })
}
}
abstract class TMNil extends TMList
def ATMNil[A] = new TMNil { type Of = A }
abstract class TMCons extends TMList {
self =>
val head: Of
val tail: TMList { type Of = self.Of }
}
ОК, определение выглядит ужасно, но это, по крайней мере, прямолинейно, и это позволяет нам написать наш метод tail
следующим образом:
def tail4(ls: TMList) =
ls match {
case _: TMNil => sys.error("Empty list")
case c: TMCons with ls.type => c.tail
}
И красота заключается в том, что это работает, поэтому мы можем писать (с head
, как вы ожидали)
val ls = 1 ::: 2 ::: ATMNil
val a = tail4(ls)
println(head4(a) * head4(a))
и Scala знают, что элемент выходного типа все еще Int
. Нам нужно было написать что-то немного забавное с TMCons with ls.type
, а Scala жалуется, что совпадение не является исчерпывающим, но этот бит кода можно было просто вставить с помощью Scala для нас, потому что, конечно, когда вы соглашаетесь на ls
любой случай должен быть ls.type
, и, конечно, совпадение является исчерпывающим.
Итак, мой вопрос: какой улов? Почему бы нам не сделать все наши полиморфные типы таким образом и просто изменить язык, чтобы синтаксис не выглядел так плохо? С какими техническими проблемами мы столкнемся?
Ясно, что существует проблема, что класс не может быть ковариантным в своих членах типа; но меня это не очень интересует; Я думаю, что это отдельный вопрос. Предположим, что на данный момент нас не интересует разница. Что еще может пойти не так?
Я подозреваю, что это может ввести новые проблемы для вывода типа (например, как я должен был определить ATMNil
для примера для работы), но я не понимаю вывод типа Scala достаточно хорошо, чтобы знать, что это будет.
изменить ответ на 0 __: Я думаю, вы, возможно, его нашли. Версия с параметром типа работает,
def move2[A](a: TMList { type Of = A }, b: TMList { type Of = A }) = b match {
case c: TMCons with b.type => c.head ::: a
case _ => a
}
Но интересно то, что без явного типа возвращаемого значения в зависимости от типа печатной версии не зависит:
def move3(a: TMList)(b: TMList { type Of = a.Of }) = b match {
case c: TMCons with b.type => c.head ::: a
case _ => a
}
Scala указывает тип возврата TMList
; это верхняя граница двух типов случая, TMList { type Of = a.Of }
и a.type
. Конечно, TMList { type Of = a.Of }
также будет верхней границей (и той, которую я хочу, и поэтому добавление явного типа возврата работает), и, я думаю, более конкретная верхняя граница. Интересно, почему Scala не делает вывод более конкретной верхней границы.