Ответ 1
Теперь это сложно. И это на самом деле довольно удивительно, я не знал, что "обходной путь" к проблеме "ленивый неявный не покрывает полный блок". Спасибо за это!
Что происходит, связано с ожидаемыми типами и как они влияют на работу типа, выводятся неявные преобразования и перегрузки.
Тип вывода и ожидаемые типы
Во-первых, мы должны знать, что вывод типа в Scala является двунаправленным. Большая часть вывода работает снизу вверх (задано a: Int
и b: Int
, infer a + b: Int
), но некоторые вещи сверху вниз. Например, вывод параметров параметров лямбда сверху вниз:
def foo(f: Int => Int): Int = f(42)
foo(x => x + 1)
Во второй строке после определения foo
как def foo(f: Int => Int): Int
тип inferencer может сказать, что x
должен иметь тип Int
. Он делает это до того, как проведет проверку самой лямбда. Он распространяет информацию о типе из приложения-приложения до лямбда, который является параметром.
Вывод сверху вниз в основном основан на понятии ожидаемого типа. Когда typechecking node AST программы, typechecker не запускается с пустыми руками. Он получает ожидаемый тип от "выше" (в данном случае приложение функции node). Когда typechecking lambda x => x + 1
в приведенном выше примере, ожидаемый тип Int => Int
, потому что мы знаем, какой тип параметра ожидается foo
. Это приводит к выходу типа для вывода Int
для параметра x
, который, в свою очередь, позволяет typecheck x + 1
.
Ожидаемые типы распространяются по некоторым конструкциям, например, блокам ({}
) и ветвям if
и match
es. Следовательно, вы также можете вызвать foo
с помощью
foo({
val y = 1
x => x + y
})
и typechecker все еще может вывести x: Int
. Это связано с тем, что при проверке типа блока { ... }
ожидаемый тип Int => Int
передается на проверку типов последнего выражения, т.е. x => x + y
.
Неявные преобразования и ожидаемые типы
Теперь мы должны ввести неявные преобразования в микс. Когда typechecking a node создает значение типа T
, но ожидаемый тип для этого node равен U
, где T <: U
- false, typechecker ищет неявный T => U
(я, вероятно, упрощаю вещи немного здесь, но суть все еще верна). Вот почему ваш первый пример не работает. Давайте посмотрим внимательно:
trait Foo[A] {
def apply(a: Apply[A]): A = a()
}
val foo = new Foo[Int] {}
foo({
i = i + 1
i
})
При вызове foo.apply
ожидаемый тип для параметра (т.е. блока) равен Apply[Int]
(A
уже был создан для Int
). Мы можем "написать" это "состояние" typechecker следующим образом:
{
i = i + 1
i
}: Apply[Int]
Этот ожидаемый тип передается до последнего выражения блока, которое дает:
{
i = i + 1
(i: Apply[Int])
}
в этой точке, так как i: Int
и ожидаемый тип Apply[Int]
, typechecker находит неявное преобразование:
{
i = i + 1
fromLazyVal[Int](i)
}
который вызывает только i
.
Перегрузки и ожидаемые типы
ОК, время для перегрузки там! Когда typechecker видит приложение метода перегрузки, у него гораздо больше проблем при выборе ожидаемого типа. Мы видим, что в следующем примере:
object Foo {
def apply(f: Int => Int): Int = f(42)
def apply(f: String => String): String = f("hello")
}
Foo(x => x + 1)
дает:
error: missing parameter type
Foo(x => x + 1)
^
В этом случае отказ typechecker в определении ожидаемого типа приводит к тому, что тип параметра не должен быть выведен.
Если мы возьмем ваше решение для вашей проблемы, мы имеем другое следствие:
trait Foo[A] {
def apply(a: Apply[A]): A = a()
def apply(s: Symbol): Foo[A] = this
}
val foo = new Foo[Int] {}
foo({
i = i + 1
i
})
Теперь, когда typechecking блок, typechecker не имеет ожидаемого типа для работы. Поэтому он будет проверять последнее выражение без выражения, и в конечном итоге typecheck весь блок как Int
:
{
i = i + 1
i
}: Int
Только теперь, с уже аргументом typechecked, он пытается разрешить перегрузки. Поскольку ни одна из перегрузок не соответствует напрямую, она пытается применить неявное преобразование от Int
к Apply[Int]
или Symbol
. Он находит fromLazyVal[Int]
, который применяется ко всему аргументу. Он больше не вставляет его внутри блока, давая:
fromLazyVal({
i = i + 1
i
}): Apply[Int]
В этом случае весь блок остеклен.
Это завершает объяснение. Резюмируя, основное различие заключается в наличии против отсутствия ожидаемого типа при проверке типа блока. При ожидаемом типе неявное преобразование максимально приближается, вплоть до i
. Без ожидаемого типа неявное преобразование применяется апостериор на весь аргумент, т.е. Весь блок.