¿Por qué los lenguajes de tipo estático más convencionales no admiten la sobrecarga de funciones / métodos por tipo de retorno? No se me ocurre ninguno que lo haga. Parece no menos útil o razonable que soportar la sobrecarga por tipo de parámetro. ¿Cómo es que es mucho menos popular?
252
Respuestas:
Al contrario de lo que otros dicen, la sobrecarga por tipo de retorno es posible y se realiza en algunos idiomas modernos. La objeción habitual es que en código como
No se puede saber cuál
func()
se llama. Esto se puede resolver de varias maneras:int main() { (string)func(); }
.Dos de los idiomas que uso regularmente ( ab ) usan sobrecarga por tipo de retorno: Perl y Haskell . Déjame describir lo que hacen.
En Perl , hay una distinción fundamental entre el contexto escalar y de lista (y otros, pero pretendemos que hay dos). Cada función incorporada en Perl puede hacer cosas diferentes dependiendo del contexto en el que se llama. Por ejemplo, el
join
operador fuerza el contexto de la lista (en la cosa que se está uniendo) mientras que elscalar
operador fuerza el contexto escalar, así que compare:Cada operador en Perl hace algo en contexto escalar y algo en contexto de lista, y pueden ser diferentes, como se ilustra. (Esto no es solo para operadores aleatorios como
localtime
. Si usa una matriz@a
en el contexto de la lista, devuelve la matriz, mientras que en el contexto escalar, devuelve el número de elementos. Entonces, por ejemplo,print @a
imprime los elementos, mientrasprint 0+@a
imprime el tamaño. ) Además, cada operador puede forzar un contexto, por ejemplo, la suma+
fuerza el contexto escalar. Cada entrada en losman perlfunc
documentos esto. Por ejemplo, aquí hay parte de la entrada paraglob EXPR
:Ahora, ¿cuál es la relación entre la lista y el contexto escalar? Bueno
man perlfunc
diceasí que no se trata simplemente de tener una sola función, y luego se realiza una conversión simple al final. De hecho, elegí el
localtime
ejemplo por ese motivo.No son solo los elementos integrados los que tienen este comportamiento. Cualquier usuario puede definir dicha función utilizando
wantarray
, lo que le permite distinguir entre lista, escalar y contexto vacío. Entonces, por ejemplo, puedes decidir no hacer nada si te llaman en un contexto vacío.Ahora, puede quejarse de que esto no es una sobrecarga verdadera por valor de retorno porque solo tiene una función, que se le dice al contexto en el que se invoca y luego actúa sobre esa información. Sin embargo, esto es claramente equivalente (y análogo a cómo Perl no permite la sobrecarga habitual literalmente, pero una función solo puede examinar sus argumentos). Además, resuelve muy bien la ambigua situación mencionada al comienzo de esta respuesta. Perl no se queja de que no sabe a qué método llamar; solo lo llama. Todo lo que tiene que hacer es averiguar en qué contexto se llamó a la función, que siempre es posible:
(Nota: a veces puedo decir operador Perl cuando me refiero a la función. Esto no es crucial para esta discusión).
Haskell adopta el otro enfoque, es decir, no tener efectos secundarios. También tiene un sistema de tipo fuerte, por lo que puede escribir código como el siguiente:
Este código lee un número de coma flotante de la entrada estándar e imprime su raíz cuadrada. Pero, ¿qué es lo sorprendente de esto? Bueno, el tipo de
readLn
esreadLn :: Read a => IO a
. Lo que esto significa es que para cualquier tipo que pueda serRead
(formalmente, cada tipo que sea una instancia de laRead
clase de tipo),readLn
puede leerlo. ¿Cómo sabía Haskell que quería leer un número de coma flotante? Bueno, el tipo desqrt
essqrt :: Floating a => a -> a
, lo que esencialmente significa quesqrt
solo puede aceptar números de punto flotante como entradas, por lo que Haskell dedujo lo que quería.¿Qué sucede cuando Haskell no puede inferir lo que quiero? Bueno, hay algunas posibilidades. Si no uso el valor de retorno, Haskell simplemente no llamará a la función en primer lugar. Sin embargo, si yo hago utilizar el valor de retorno, a continuación, Haskell se quejará de que no se puede inferir el tipo:
Puedo resolver la ambigüedad especificando el tipo que quiero:
De todos modos, lo que significa toda esta discusión es que la sobrecarga por valor de retorno es posible y se hace, lo que responde parte de su pregunta.
La otra parte de su pregunta es por qué más idiomas no lo hacen. Dejaré que otros respondan eso. Sin embargo, algunos comentarios: la razón principal es probablemente que la oportunidad de confusión es realmente mayor aquí que en la sobrecarga por tipo de argumento. También puede ver los fundamentos de idiomas individuales:
Ada : "Puede parecer que la regla de resolución de sobrecarga más simple es usar todo, toda la información de un contexto lo más amplio posible, para resolver la referencia sobrecargada. Esta regla puede ser simple, pero no es útil. Requiere el lector humano para escanear fragmentos de texto arbitrariamente grandes y para hacer inferencias arbitrariamente complejas (como (g) arriba). Creemos que una mejor regla es aquella que hace explícita la tarea que debe realizar un lector humano o un compilador, y eso hace que esta tarea tan natural para el lector humano como sea posible ".
C ++ (subsección 7.4.1 de "El lenguaje de programación C ++" de Bjarne Stroustrup): "Los tipos de retorno no se consideran en la resolución de sobrecarga. El motivo es mantener la resolución de un operador individual o llamada de función independiente del contexto. Considere:
Si se tuviera en cuenta el tipo de retorno, ya no sería posible mirar una llamada de forma
sqrt()
aislada y determinar qué función se llamó ". (Tenga en cuenta, para comparar, que en Haskell no hay conversiones implícitas ).Java ( Java Language Specification 9.4.1 ): "Uno de los métodos heredados debe ser sustituible por el tipo de retorno para cualquier otro método heredado, de lo contrario se produce un error en tiempo de compilación". (Sí, sé que esto no da una razón. Estoy seguro de que la razón es dada por Gosling en "el lenguaje de programación Java". ¿Quizás alguien tiene una copia? Apuesto a que es el "principio de la menor sorpresa" en esencia. ) Sin embargo, un dato curioso sobre Java: ¡la JVM permite la sobrecarga por valor de retorno! Esto se usa, por ejemplo, en Scala , y también se puede acceder directamente a través de Java jugando con elementos internos.
PD. Como nota final, en realidad es posible sobrecargar por valor de retorno en C ++ con un truco. Testigo:
fuente
Foo
yBar
soporte de conversión bidireccional, y un método usa el tipoFoo
internamente pero devuelve el tipoBar
. Si se llama a dicho método mediante un código que obligará de inmediato a escribir el resultadoFoo
, usar elBar
tipo de retorno podría funcionar, peroFoo
sería mejor. Por cierto, también me gustaría ver un medio por el cual ...var someVar = someMethod();
(o de lo contrario designar que su retorno no debería usarse de esa manera). Por ejemplo, una familia de tipos que implementa una interfaz Fluent podría beneficiarse de tener versiones mutables e inmutables, porvar thing2 = thing1.WithX(3).WithY(5).WithZ(9);
lo que tendría queWithX(3)
copiarthing1
a un objeto mutable, mutar X y devolver ese objeto mutable;WithY(5)
mutaría Y y devolvería el mismo objeto; asimismo `WithZ (9). Entonces la asignación se convertiría en un tipo inmutable.Si el tipo de retorno sobrecargó las funciones y usted tuvo estas dos sobrecargas
no hay forma de que el compilador pueda determinar a cuál de esas dos funciones llamar al ver una llamada como esta
Por esta razón, los diseñadores de idiomas a menudo no permiten la sobrecarga del valor de retorno.
Algunos idiomas (como MSIL), sin embargo, no permiten la sobrecarga según el tipo de retorno. También se enfrentan a la dificultad anterior, por supuesto, pero tienen soluciones alternativas, por lo que deberá consultar su documentación.
fuente
En tal lenguaje, ¿cómo resolvería lo siguiente:
si
f
tuvo sobrecargasvoid f(int)
yvoid f(string)
yg
tuvo sobrecargasint g(int)
ystring g(int)
? Necesitarías algún tipo de desambigador.Creo que las situaciones en las que podría necesitar esto se resolverían mejor eligiendo un nuevo nombre para la función.
fuente
Para robar una respuesta específica de C ++ de otra pregunta muy similar (¿engañado?):
Los tipos de retorno de funciones no entran en juego en la resolución de sobrecarga simplemente porque Stroustrup (supongo que con la entrada de otros arquitectos de C ++) quería que la resolución de sobrecarga fuera 'independiente del contexto'. Consulte 7.4.1 - "Tipo de sobrecarga y retorno" del "Lenguaje de programación C ++, tercera edición".
Querían que se basara solo en cómo se llamaba la sobrecarga, no en cómo se usaba el resultado (si se usaba). De hecho, se llaman muchas funciones sin usar el resultado o el resultado se usaría como parte de una expresión más grande. Un factor que estoy seguro entró en juego cuando decidieron que esto era que si el tipo de retorno era parte de la resolución, habría muchas llamadas a funciones sobrecargadas que tendrían que resolverse con reglas complejas o tendrían que hacer que el compilador arroje Un error de que la llamada era ambigua.
Y, Dios sabe, la resolución de sobrecarga de C ++ es lo suficientemente compleja como está ...
fuente
En Haskell es posible aunque no tenga sobrecarga de funciones. Haskell usa clases de tipo. En un programa puedes ver:
La sobrecarga de funciones en sí no es tan popular. La mayoría de los lenguajes que he visto con él son C ++, quizás java y / o C #. En todos los lenguajes dinámicos es una abreviatura para:
Por lo tanto, no tiene mucho sentido. A la mayoría de las personas no les interesa si el lenguaje puede ayudarlo a soltar una sola línea por donde lo use.
La coincidencia de patrones es algo similar a la sobrecarga de funciones, y supongo que a veces funcionan de manera similar. Sin embargo, no es común porque es útil solo para algunos programas y es difícil de implementar en la mayoría de los idiomas.
Verá que hay infinitas funciones más fáciles de implementar para implementar en el lenguaje, que incluyen:
fuente
Buenas respuestas! La respuesta de A.Rex en particular es muy detallada e instructiva. Como él señala, C ++ no consideran operadores tipo de conversión suministrados por el usuario al compilar
lhs = func();
(donde func es realmente el nombre de una estructura) . Mi solución es un poco diferente, no mejor, solo diferente (aunque se basa en la misma idea básica).Mientras que yo quería escribir ...
Terminé con una solución que usa una estructura parametrizada (con T = el tipo de retorno):
Una ventaja de esta solución es que cualquier código que incluya estas definiciones de plantilla puede agregar más especializaciones para más tipos. También puede hacer especializaciones parciales de la estructura según sea necesario. Por ejemplo, si desea un manejo especial para los tipos de puntero:
Como negativo, no puedes escribir
int x = func();
con mi solución. Tienes que escribirint x = func<int>();
. Debe decir explícitamente cuál es el tipo de retorno, en lugar de pedirle al compilador que lo analice mirando los operadores de conversión de tipo. Diría que "mi" solución y las de A.Rex pertenecen a un frente pareto-óptimo de formas de abordar este dilema de C ++ :)fuente
si desea sobrecargar los métodos con diferentes tipos de retorno, simplemente agregue un parámetro ficticio con el valor predeterminado para permitir la ejecución de la sobrecarga, pero no olvide que el tipo de parámetro debe ser diferente para que la lógica de sobrecarga funcione a continuación, por ejemplo, en delphi:
úsalo así
fuente
Como ya se mostró, las llamadas ambiguas de una función que difiere solo por el tipo de retorno introducen ambigüedad. La ambigüedad induce código defectuoso. El código defectuoso debe ser evitado.
La complejidad impulsada por el intento de ambigüedad muestra que este no es un buen truco. Además de un ejercicio intelectual, ¿por qué no utilizar procedimientos con parámetros de referencia?
fuente
doing(thisVery(deeplyNested(), andOften(butNotAlways()), notReally()), goodCode());
Esta característica de sobrecarga no es difícil de administrar, si la miras de una manera ligeramente diferente. considera lo siguiente,
si un idioma devolviera la sobrecarga, permitiría la sobrecarga de parámetros, pero no las duplicaciones. Esto resolvería el problema de:
porque solo hay una f (int elección) para elegir.
fuente
En .NET, a veces usamos un parámetro para indicar la salida deseada de un resultado genérico, y luego realizamos una conversión para obtener lo que esperamos.
C#
Quizás este ejemplo también podría ayudar:
C ++
fuente
Para el registro, Octave permite un resultado diferente según el elemento de retorno es escalar frente a matriz.
Cf también Descomposición del valor singular .
fuente
Este es ligeramente diferente para C ++; No sé si se consideraría una sobrecarga por tipo de retorno directamente. Es más una plantilla de especialización que actúa de la misma manera.
util.h
util.inl
util.cpp
Este ejemplo no utiliza exactamente la resolución de sobrecarga de funciones por tipo de retorno, sin embargo, esta clase de no objeto de C ++ está utilizando la especialización de plantilla para simular la resolución de sobrecarga de funciones por tipo de retorno con un método estático privado.
Cada una de las
convertToType
funciones llama a la plantilla de funciónstringToValue()
y si observa los detalles de implementación o el algoritmo de esta plantilla de función, llamagetValue<T>( param, param )
y devuelve un tipoT
y lo almacena en unoT*
que se pasa a lastringToValue()
plantilla de función como uno de sus parámetros .Aparte de algo como esto; C ++ realmente no tiene un mecanismo para tener una resolución de sobrecarga de funciones por tipo de retorno. Puede haber otras construcciones o mecanismos que no conozco que podrían simular la resolución por tipo de retorno.
fuente
Creo que esto es un GAP en la definición moderna de C ++ ... ¿por qué?
¿Por qué un compilador de C ++ no puede arrojar un error en el ejemplo "3" y aceptar el código en el ejemplo "1 + 2"?
fuente
La mayoría de los lenguajes estáticos ahora también admiten genéricos, lo que resolvería su problema. Como se indicó anteriormente, sin tener diferencias de parámetros, no hay forma de saber a cuál llamar. Entonces, si quieres hacer esto, solo usa genéricos y llámalo por día.
fuente