El siguiente ejemplo es perfectamente legal en Kotlin 1.3.21:
fun <T> foo(bar: T): T = bar val t: Int = foo(1) // No need to declare foo<Int>(1) explicitlyPero, ¿por qué la inferencia de tipos no funciona para funciones de orden superior?
fun <T> foo() = fun(bar: T): T = bar val t: Int = foo()(1) // Compile error: Type inference failed...Cuando se utilizan funciones de orden superior, Kotlin obliga a que el sitio de la llamada sea:
val t = foo<Int>()(1) Incluso si el tipo de retorno de foo se especifica explícitamente, la inferencia de tipo sigue fallando:
fun <T> foo(): (T) -> T = fun(bar: T): T = bar val t: Int = foo()(1) // Compile error: Type inference failed...Sin embargo, cuando el parámetro de tipo genérico se comparte con la función externa, ¡funciona!
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... ¿Cómo escribo la función foo para que foo()(1) se compile, donde bar es un tipo genérico?
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.
Así que según tengo entendido es que:
foo() <- usando información de tipo aquí
foo()(1) <- proporcionando la información aquí
Parece que la inferencia de tipos no funciona 'hacia atrás'
val foo = foo<Int>()//create function val bar = foo(1)//call functionPara decirlo en términos simples (posiblemente demasiado simplificados), cuando llama a una función generada dinámicamente, como el valor de retorno de una función de orden superior, en realidad no es una llamada de función, es solo azúcar sintáctica para la función de invoke .
En el nivel de sintaxis, Kotlin trata los objetos con tipos de retorno como () -> A y (A, B) -> C como si fueran funciones normales; le permite llamarlos simplemente adjuntando argumentos entre paréntesis. Esta es la razón por la que puede hacer foo<Int>()(1) - foo<Int>() devuelve un objeto de tipo (Int) -> (Int) , que luego se llama con 1 como argumento.
Sin embargo, bajo el capó, estos "objetos de función" no son realmente funciones, son simplemente objetos simples con un método de operador de invoke . Entonces, por ejemplo, los objetos de función que toman 1 argumento y devuelven un valor son realmente solo instancias de la interfaz especial Function1 que se parece a esto
interface Function1<A, R> { operator fun invoke(a: A): R } Cualquier clase con operator fun invoke se puede llamar como una función, es decir, en lugar de foo.invoke(bar, baz) puede simplemente llamar a foo(bar, baz) . Kotlin tiene varias clases integradas como esta llamada Function , Function1 , Function2 , Function<number of args> etc. que se utilizan para representar objetos de función. Entonces, cuando llamas a foo<Int>()(1) , lo que en realidad estás llamando es foo<Int>().invoke(1) . Puede confirmar esto descompilando el código de bytes.
Entonces, ¿qué tiene esto que ver con la inferencia de tipo? Bueno, cuando llamas a foo()(1) , en realidad estás llamando a foo().invoke(1) con un poco de azúcar sintáctica, lo que hace que sea un poco más fácil ver por qué falla la inferencia. El lado derecho del operador punto no se puede usar para inferir tipos para el lado izquierdo, porque el lado izquierdo debe evaluarse primero. Entonces, el tipo de foo debe indicarse explícitamente como foo<Int> .
Solo jugué un poco con él y compartimos algunos pensamientos, básicamente respondiendo a la última pregunta "¿Cómo escribo la función foo para que foo()(1) se compile, donde bar es un tipo genérico?":
Una solución simple, pero luego renuncia a su función de orden superior (o necesita envolverla) es tener un objeto intermediario en su lugar, por ejemplo:
object FooOp { operator fun <T> invoke(t : T) = t } con un método foo similar al siguiente:
fun foo() = FooOpPor supuesto, eso no es realmente lo mismo, ya que básicamente trabajas alrededor de la primera función genérica. Básicamente, es casi lo mismo que tener solo 1 función que devuelve el tipo que queremos y, por lo tanto, también puede inferir el tipo nuevamente.
Una alternativa a tu problema podría ser la siguiente. Simplemente agregue otra función que realmente especifique el tipo:
fun <T> foo() = fun(bar: T): T = bar @JvmName("fooInt") fun foo() = fun(bar : Int) = barEntonces tendrán éxito los dos siguientes:
val t: Int = foo()(1) val t2: String = foo<String>()("...")pero... (además de necesitar potencialmente muchas sobrecargas) no es posible definir otra función similar a la siguiente:
@JvmName("fooString") fun foo() = fun(bar : String) = barSi define esa función, le dará un error similar al siguiente:
Conflicting overloads: @JvmName public final fun foo(): (Int) -> Int defined in XXX, @JvmName public final fun foo(): (String) -> String defined in XXX¿Pero tal vez eres capaz de construir algo con eso?
De lo contrario, no tengo una respuesta de por qué se infiere y por qué no.