Quick Quiz: default params y extensions

#1

Les dejo este quiz cortito de twitter sobre con comportamiento loco de extensions y default params, respondan lo que honestamente creen que da ese código no piensen que seguro hay una trampa dando vuelta:

(abran el link para poder votar en tuiter)

Después discutimos acá abajo por qué pasa eso.

1 me gusta
#2
Spoiler! No hagas click en este texto (!?)

Imprimir 1 suena como el resultado esperado pero me inclino que va por el compile error.

1 me gusta
#3

Efectivamente Imprime 1

No estoy muy seguro de porqué la extension se comporta de esa manera, pero otros devs en el proyecto estaban usando ese “HACK” para poder tener funciones con parámetros default en funciones especificadas en el protocolo.

El problema que tuvimos es que yo modifiqué la función original le agregué un nuevo parametro y la extension termino haciendo un loop infinito, excepto que en MI MAQUINA no hacia loop, ya que como tenia compilar de manera incremental por algún motivo no hacia loop infinito sino que funcionaba bien, no fue hasta que hice un clean que empezó a tirar loops infinitos.

Lo que hice fue modificar las extensiones que hacian estos hacks de la siguiente manera:

extension P {
    func f() {
        f(p: 1)
    }
}

De esta manera no hay loop infinito y seguimos teniendo la función con parámetros default.

Anyway, le tire un tweet a @jckarter que me mandó a preguntarle a @dgregor79 y a @slava_pestov, así que quizás pronto tengamos una respuesta real :crossed_fingers:

  • BUG :beetle:
  • FEATURE :tophat:

0 votantes

#4

Busqué entre los bugs de Swift y no lo encontré, ¿pensás agregarlo?

#5

Yo desde mi humilde lugar de “amateur” en el lenguaje no veo nada extraño, ya que en este caso la clase no implementa ningún método que responda al signature de f() por lo tanto responde la implementación por default del protocolo (static dispatch). En cambio, como la clase sí implementa un método con el signature de f(p: Int) el método que se llama es el de la clase (dynamic dispatch).

Obvio que no estoy viendo algo… Será que lo extraño ocurre cuando decís que modificaste la función original? Cuál sería la función original? Podés mostrarnos aunque sea los signatures?

#6

Acá está la tuiter charla que tuve con @slava_pestov https://twitter.com/slava_pestov/status/1111404152150605825 con más detalles, al parecer no lo consideran un bug aunque si algo confuso y error prone por lo que Slava consideraría algún tipo de warning.

@sebas la parte que confunde es que dentro de la extension definís otra función que tiene la misma firma f(p: Int), y cuando ahi adentro llamás a f(p:p) el dynamic llama a la otra función a la de C y no a la de la extension. Para mi gusto es un comportamiento implicito y por lo tanto confuso en el cual el compilador no te advierte de nada.

Voy a explayarme más, suponete que en esa extension querías implementar una función recursiva, el dynamic dispatch te llama a la otra implementación de f(p: Int), pensá que este ejemplo es muy trivial pero si tenés una clase grande y f(p: Int) tiene varios parámetros y tenés varias versiones de f(p: Int) podes terminar sin querer en un f(p: Int) llamando a otra sin saberlo por que el compilador jamás te avisa y no es un feature intencional del lenguaje que a la hora de hacer dispatch de llamadas a funciones tengan prioridad primero las que no tienen parámetros default, y si por alguna coincidencia de la vida esa otra f(p: Int) que llamás sin querer sirve no te das cuenta hasta que un día venís haces un cambio y el cambio no repercute en la ejecución, o peor ahora se llama otra versión de f(p: Int) distinta.

En mi caso en particular, yo modifique la función del protocolo agregando un nuevo parametro, entonces el compilador me hizo ir y modificar f(p: Int) en la clase C, pero de la extension y de la f(p: Int) de ahí no me dijo nada y entonces al correr la app y caer en el flujo que usaba la funcion sin parametros PUM! infinite loop :stuck_out_tongue:

En un momento pensé que lo correcto sería que el compilador avisé que hay 2 funciones iguales, C::f(p: Int) y P::f(p: Int = 1) pero esto tampoco es correcto por que eso es lo que hacen las extensiones y además la extension es sobre P nisiquiera es sobre C, entonces no es sencillo.

Esto tiene toda la pinta de comportamiento emergente.

#7

Muchas gracias @Julito por tu explicación y la referencia!.

Yo creo que la diferencia está acá

Y para mí no es la misma firma, ya que f(p: Int) y f(p: Int = 1) no son iguales porque f(p: Int = 1) implica f() y f(p: Int). Como vos estás llamando a f() y la clase no la implementa, llama a la de la extensión.

Acá vuelvo a lo mismo, no son la misma firma. Una tiene dos firmas y la otra tiene una.

Volviendo a la pregunta original, sobre si es bug o feature, yo no lo considero un bug, pero tampoco sé si en este caso es una feature, sino que es el resultado esperable. Claramente es confuso usarlo de esta manera y creo que tu enfoque para resolverlo es un excelente camino, ya que estás haciendo explícita la firma f() que antes no lo era.

#8

Igual, estamos de acuerdo que el compilador no debería marearse ¿no? :bug: :joy:

Lo que no entendí es porqué te hacía infinite loop.

#9

El infinite loop es porque modifique f(p:Int) en la clase C.

Si copias ese código en un Playground y luego modificas f(p: Int) en C a por ejemplo f(p: Int, s: String) verás que todo sigue compilando y no hay warnings, pero al correr hace loop.