Вывод типа для функций более высокого порядка с общими типами возврата - PullRequest
0 голосов
/ 21 февраля 2019

Следующий пример является полностью допустимым в Kotlin 1.3.21:

fun <T> foo(bar: T): T = bar

val t: Int = foo(1) // No need to declare foo<Int>(1) explicitly

Но почему вывод типа не работает для функций более высокого порядка?

fun <T> foo() = fun(bar: T): T = bar

val t: Int = foo()(1) // Compile error: Type inference failed...

При использовании более высокого порядкафункции, Kotlin заставляет сайт вызова быть:

val t = foo<Int>()(1)

Даже если тип возврата foo указан явно, вывод типа по-прежнему не удается:

fun <T> foo(): (T) - > T = fun(bar: T): T = bar

val t: Int = foo()(1) // Compile error: Type inference failed...

Однако, когдаПараметр универсального типа используется совместно с внешней функцией, он работает!

fun <T> foo(baz: T) = fun (bar: T): T = bar

val t: Int = foo(1)(1) // Horray! But I want to write foo()(1) instead...

Как написать функцию foo, чтобы компиляция foo()(1), где bar - универсальный тип?

Ответы [ 3 ]

0 голосов
/ 01 апреля 2019

Просто немного поигрался с этим и поделился некоторыми мыслями, в основном отвечая на последний вопрос: «Как мне написать функцию foo, чтобы foo()(1) скомпилировался, где bar - универсальный тип?»:

Простой обходной путь, но затем вы отказываетесь от функции более высокого порядка (или вам нужно обернуть ее), чтобы иметь промежуточный объект на месте, например:

object FooOp {
  operator fun <T> invoke(t : T) = t
}

с foo-метод, подобный следующему:

fun foo() = FooOp

Конечно, это не совсем то же самое, поскольку вы в основном работаете с первой универсальной функцией.По сути, это почти то же самое, что просто иметь функцию 1, которая возвращает желаемый тип, и поэтому она также может выводить тип снова.

Альтернативой вашей проблеме может быть следующее.Просто добавьте еще одну функцию, которая на самом деле указывает тип:

fun <T> foo() = fun(bar: T): T = bar
@JvmName("fooInt")
fun foo() = fun(bar : Int) = bar

Следующие два затем будут успешными:

val t: Int = foo()(1)
val t2: String = foo<String>()("...")

, но ... (помимо потенциально требующих большого количества перегрузок) это не такМожно определить другую функцию, подобную следующей:

@JvmName("fooString")
fun foo() = fun(bar : String) = bar

Если вы определите эту функцию, она выдаст вам ошибку, подобную следующей:

Conflicting overloads: @JvmName public final fun foo(): (Int) -> Int defined in XXX, @JvmName public final fun foo(): (String) -> String defined in XXX

Но, возможно, вы сможетепостроить что-то с этим?

В противном случае у меня нет ответа на вопрос, почему оно выведено, а почему нет.

0 голосов
/ 07 апреля 2019

Проще говоря, если вы вызываете динамически сгенерированную функцию, например, возвращаемое значение функции более высокого порядка, на самом деле это не вызов функции, а просто синтаксический сахар для invoke function.

На уровне синтаксиса Kotlin обрабатывает объекты с возвращаемыми типами, такими как () -> A и (A, B) -> C, как они являются обычными функциями - он позволяет вам вызывать их, просто добавляя аргументы в скобках.Вот почему вы можете сделать foo<Int>()(1) - foo<Int>() возвращает объект типа (Int) -> (Int), который затем вызывается с 1 в качестве аргумента.

Однако, под капотом эти "функциональные объекты"на самом деле это не функции, это просто простые объекты с операторным методом invoke.Так, например, функциональные объекты, которые принимают 1 аргумент и возвращают значение, на самом деле являются просто экземплярами специального интерфейса Function1, который выглядит примерно так

interface Function1<A, R> {
    operator fun invoke(a: A): R
}

Любой класс с operator fun invoke можно назвать както есть вместо foo.invoke(bar, baz) вы можете просто позвонить foo(bar, baz).Kotlin имеет несколько встроенных классов, таких как Function, Function1, Function2, Function<number of args> и т. Д., Используемых для представления функциональных объектов.Поэтому, когда вы звоните foo<Int>()(1), вы на самом деле звоните foo<Int>().invoke(1).Вы можете подтвердить это, декомпилировав байт-код.

Decompiled Kotlin bytecode

Так, как это связано с выводом типа?Хорошо, когда вы звоните foo()(1), вы на самом деле звоните foo().invoke(1) с небольшим синтаксическим сахаром, что немного облегчает понимание того, почему вывод не удался.Правая сторона оператора точки не может использоваться для вывода типов для левой стороны, потому что левая сторона должна быть оценена первой.Таким образом, тип для foo должен быть явно указан как foo<Int>.

0 голосов
/ 21 февраля 2019

I am not an expert on how type inference works, but the basic rule is: At the point of use the compiler must know all types in the expression being used.

Итак, насколько я понимаю, это:

foo () <- используя информацию о типе здесь </p>

foo () (1) <- предоставлениеинформация здесь </p>

Похоже, что вывод типа не работает "назад"

    val foo = foo<Int>()//create function
    val bar = foo(1)//call function
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...