Предположим, мы определили экзистенциальный тип:
type T = (X => X, X) forSome { type X }
а затем определил список типа List[T]:
val list = List[T](
((x: Int) => x * x, 42),
((_: String).toUpperCase, "foo")
)
Хорошо известно [1], [2], что следующая попытка map не работает:
list.map{ x => x._1(x._2) }
Но тогда почему работает следующее ?:
list.map{ case x => x._1(x._2) }
Обратите внимание, что ответы на оба связанных вопроса предполагают, что при сопоставлении с образцом требуется переменная типа, но она также работает без переменной типа. Акцент вопроса больше на Почему работает { case x => ... }?.





(Моя собственная попытка ответить на вопрос; должно быть не так уж и неправильно, но, возможно, немного поверхностно.)
Во-первых, заметьте, что
list.map{ x => x._1(x._2) }
list.map{ case x => x._1(x._2) }
по сути то же самое, что
list map f1
list map f2
с участием
val f1: T => Any = t => t._1(t._2)
val f2: T => Any = _ match {
case q => q._1(q._2)
}
Действительно, компиляция f1 завершается неудачно, тогда как f2 завершается успешно.
Мы можем понять, почему компиляция f1 терпит неудачу:
t относится к типу (X => X, X) forSome { type X }.t._1 имеет тип (X => X) forSome { type X }.t._2 имеет тип X forSome { type X }, то есть просто Any.(X => X) forSome { type X } к Any, потому что на самом деле он может оказаться (SuperSpecialType => SuperSpecialType) для некоторого SuperSpecialType.Следовательно, компиляция f1 должна завершиться неудачей, и действительно не удастся.
Чтобы понять, почему f2 успешно компилируется, можно посмотреть результат проверки типов. Если мы сохраним это как someFile.scala:
class O {
type T = (X => X, X) forSome { type X }
def f2: T => Any = t => t match {
case q => q._1(q._2)
}
def f2_explicit_func_arg: T => Any = t => t match {
case q => {
val f = q._1
val x = q._2
f(x)
}
}
}
а затем сгенерируйте вывод проверки типов с помощью
$ scalac -Xprint:typer someFile.scala
мы получаем по существу (с удалением некоторого шума):
class O extends scala.AnyRef {
type T = (X => X, X) forSome { type X };
def f2: O.this.T => Any = ((t: O.this.T) => t match {
case (q @ _) => q._1.apply(q._2)
});
def f2_explicit_func_arg: O.this.T => Any = ((t: O.this.T) => t match {
case (q @ _) => {
val f: X => X = q._1;
val x: X = q._2;
f.apply(x)
}
})
}
Вторая версия f2_explicit_func_arg (эквивалент f2) более информативна, чем более короткая оригинальная версия f2. В коде f2_explicit_func_arg без сахара и с проверкой типа мы видим, что тип X чудесным образом появляется снова, и проверка типов действительно делает вывод:
f: X => X
x: X
так что f(x) действительно действителен.
В более очевидном обходном пути с явно названной переменной типа мы делаем вручную то, что компилятор делает за нас в этом случае.
Мы также могли бы написать:
type TypeCons[X] = (X => X, X)
list.map{ case t: TypeCons[x] => t._1(t._2) }
или даже более явно:
list.map{ case t: TypeCons[x] => {
val func: x => x = t._1
val arg: x = t._2
func(arg)
}}
и обе версии будут компилироваться по тем же причинам, что и f2.
@JoePallas Я не уверен, как здесь должна помочь вспомогательная функция, по крайней мере, list map helper не работает. Я также не уверен, на каком уровне вы хотели бы получить объяснение «почему»: я надеюсь, что мне удалось прояснить, почему list.map{x => ...} не должен работать на любом вообразимом языке. Аргумент, запрещающий list.map{x => ...}, разваливается, как только вводится дополнительный этап сопоставления с образцом, поэтому нет никаких «теоретических» причин, по которым он не должен работать. Действительно, на практике Scala удается прикрепить больше информации о типе к согласованной переменной.
@JoePallas [... продолжение ...] "Почему Scala удается вывести всевозможные полезные ограничения на типы переменных, используемых при сопоставлении с образцом?" Я не знаю, как на него ответить, потому что 1) я недостаточно знаю о внутреннем устройстве Scala сопоставления с образцом или вывода типа 2) я не знаю, как далеко мне нужно разложить объяснение «почему» до тех пор, пока кто-то другой принимает все крошечные под-аргументы как должное, как аксиомы.
@AndreyTyukin Попробуй list.map(helper(_)) или list.map(t => helper(t)).
@AlexeyRomanov Да, все работает, спасибо! Комментарий под ваш ответ здесь говорит именно об этом. Но, похоже, это работает по другой причине, чем сопоставление с образцом: вы применяете универсально количественную функцию к экзистенциально количественно определяемому аргументу, что полностью имеет смысл. Есть ли какая-то общая тема между универсальной вспомогательной функцией и сопоставлением с образцом, которая помогает лучше объяснить «тип X чудесным образом снова появляется»?
Я помню, что была некоторая информация об этом в блоге Typelevel, но я не помню, где именно. typelevel.org/blog/2015/07/13/type-members-parameters.html должен быть отправной точкой.
@AlexeyRomanov Я читал статью (по крайней мере, большую ее) несколько раз, боюсь, что она слишком далека от конкретной темы вопроса и, вероятно, не даст ответа для "Почему?" это менее расплывчато, чем «Scala имеет мощную систему типов, она может выводить много всего». Думаю, я найду утешение в цитате «Scala достаточно велика, чтобы очень немногие ее понимали» и приму свои собственные каракули в качестве ответа. Приму повторно, если кто-то предоставит примерно такой же подробный ответ и внесет то, чего я не знал.
@AndreyTyukin Под "отправной точкой" я подразумеваю, что это первый пост из довольно длинной серии, и, если я правильно помню, есть что-то, что является близко к этому вопросу в одном или нескольких сообщениях.
@AlexeyRomanov Да, я об этом и думал. Он находится в второй пост серии. «Когда мы вызываем вариант с параметризацией типа для реализации экзистенциального варианта… мы просто помогаем компилятору… и scalac, и javac умудряются сделать вывод, что тип T должен быть (иначе невыразимым) экзистенциальным».
Мне действительно жаль, что я не понял Зачем, это то, что сопоставление с образцом делает это так, как не делает вывод обычного типа. Я знаю, что обычная вспомогательная функция может делать то же самое:
def helper[S](q: (S => S, S)) = q._1(q._2). Я где-то видел обсуждение этого вопроса, но не могу вспомнить где.