Подтвердить что ты не робот

Можно ли использовать члены типа для уменьшения многословности текста в Scala?

Итак, это может звучать как общий вопрос о дизайне языка, но я думаю, что здесь есть что-то конкретное. В частности, меня интересуют, какие технические проблемы препятствуют тому, что код 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 не делает вывод более конкретной верхней границы.

4b9b3361

Ответ 1

Попробуйте переписать следующее с помощью TMList:

def move[A](a: List[A], b: List[A]): List[A] = b match {
   case head :: _ => head :: a
   case _ => a
}

move(List(1,2,3),List(4,5,6))