¿Sobrecarga de funciones? Sí o no [cerrado]

16

Estoy desarrollando un lenguaje compilado estáticamente y fuertemente tipado, y estoy revisando la idea de si incluir la sobrecarga de funciones como una característica del lenguaje. Me di cuenta de que soy un poco parcial, principalmente de un C[++|#]fondo.

¿Cuáles son las mayoría de los argumentos convincentes para y contra incluyendo la sobrecarga de funciones en un idioma?


EDITAR: ¿No hay nadie que tenga una opinión opuesta?

Bertrand Meyer (creador de Eiffel en 1985/1986) llama al método sobrecargando esto: (fuente)

un mecanismo de vanidad que no aporta nada al poder semántico de un lenguaje OO, pero dificulta la legibilidad y complica la tarea de todos

Ahora, esas son algunas generalizaciones generales, pero es un tipo inteligente, por lo que creo que es seguro decir que podría respaldarlas si fuera necesario. De hecho, casi hizo que Brad Abrams (uno de los desarrolladores de CLSv1) se convenciera de que .NET no debería admitir la sobrecarga de métodos. (fuente) Eso es algo poderoso. ¿Alguien puede arrojar algo de luz sobre sus pensamientos y si su punto de vista todavía está justificado 25 años después?

Nota para uno mismo - piense en un nombre
fuente

Respuestas:

24

La sobrecarga de funciones es absolutamente crítica para el código de plantilla de estilo C ++. Si tengo que usar diferentes nombres de funciones para diferentes tipos, no puedo escribir código genérico. Eso eliminaría una parte grande y muy utilizada de la biblioteca de C ++, y gran parte de la funcionalidad de C ++.

Suele estar presente en los nombres de las funciones miembro. A.foo()puede llamar a una función completamente diferente de B.foo(), pero ambas funciones tienen nombre foo. Está presente en los operadores, al igual +que diferentes cosas cuando se aplica a números enteros y en coma flotante, y a menudo se usa como operador de concatenación de cadenas. Parece extraño no permitirlo también en funciones regulares.

Permite el uso de "métodos múltiples" de estilo Lisp común, en los cuales la función exacta llamada depende de dos tipos de datos. Si no ha programado en Common Lisp Object System, pruébelo antes de llamarlo inútil. Es vital para las transmisiones en C ++.

La E / S sin sobrecarga de funciones (o funciones variables, que son peores) requeriría una serie de funciones diferentes, ya sea para imprimir valores de diferentes tipos o para convertir valores de diferentes tipos a un tipo común (como String).

Sin sobrecarga de funciones, si cambio el tipo de alguna variable o valor, necesito cambiar todas las funciones que lo usan. Hace mucho más difícil refactorizar el código.

Facilita el uso de API cuando el usuario no tiene que recordar qué tipo de convención de nomenclatura está en uso, y el usuario solo puede recordar los nombres de funciones estándar.

Sin la sobrecarga del operador, tendríamos que etiquetar cada función con los tipos que usa, si esa operación base se puede usar en más de un tipo. Esta es esencialmente la notación húngara, la mala forma de hacerlo.

En general, hace que un lenguaje sea mucho más útil.

David Thornley
fuente
1
+1, todos muy buenos puntos. Y créanme, no creo que los métodos múltiples sean inútiles ... Maldigo el teclado que escribo cada vez que me veo obligado a usar el patrón de visitante.
Nota para uno mismo - piense en un nombre
¿No está describiendo este tipo la función de anular y no sobrecargar?
dragosb
8

Recomiendo al menos estar al tanto de las clases de tipos en Haskell. Las clases de tipos se crearon para ser un enfoque disciplinado de la sobrecarga del operador, pero han encontrado otros usos y, hasta cierto punto, han convertido a Haskell en lo que es.

Por ejemplo, aquí hay un ejemplo de sobrecarga ad-hoc (Haskell no bastante válido):

(==) :: Int -> Int -> Bool
x == y = ...
x /= y = not (x == y)

(==) :: Char -> Char -> Bool
x == y = ...
x /= y = not (x == y)

Y aquí está el mismo ejemplo de sobrecarga con clases de tipo:

class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

    x /= y  =  not (x == y)

instance Eq Int where
    x == y  = ...

instance Eq Char where
    x == y  = ...

Una desventaja de esto es que usted tiene que llegar a nombres muy exóticos para todas sus clases de tipos (como en Haskell, usted tiene la más bien abstracta Monad, Functor, Applicativeasí como el más simple y más reconocible Eq, Numy Ord).

Una ventaja es que, una vez que está familiarizado con una clase de tipos, sabe cómo usar cualquier tipo en esa clase. Además, es fácil proteger las funciones de los tipos que no implementan las clases necesarias, como en:

group :: (Eq a) => [a] -> [[a]]
group = groupBy (==)

Editar: en Haskell, si desea un ==operador que acepte dos tipos diferentes, puede usar una clase de tipo multiparamétrica:

class Eq a b where
    (==) :: a -> b -> Bool
    (/=) :: a -> b -> Bool

    x /= y  =  not (x == y)

instance Eq Int Int where
    x == y  = ...

instance Eq Char Char where
    x == y  = ...

instance Eq Int Float where
    x == y  = ...

Por supuesto, esta es probablemente una mala idea, ya que explícitamente te permite comparar manzanas y naranjas. Sin embargo, es posible que desee considerar esto +, ya que agregar a Word8a Intrealmente es algo sensato en algunos contextos.

Joey Adams
fuente
+1, he estado tratando de comprender este concepto desde que lo leí por primera vez, y esto ayuda. ¿Este paradigma hace imposible definir, por ejemplo, en (==) :: Int -> Float -> Boolcualquier lugar? (independientemente de si es una buena idea, por supuesto)
Nota personal - piense en un nombre
Si permite clases de tipos de parámetros múltiples (que Haskell admite como una extensión), puede hacerlo. Actualicé la respuesta con un ejemplo.
Joey Adams
Mmm interesante. Básicamente, class Eq a ...traducido a pseudo-C-family sería interface Eq<A> {bool operator==(A x, A y);}, y en lugar de usar código con plantilla para comparar objetos arbitrarios, usa esta 'interfaz'. ¿Está bien?
Nota para uno mismo - piense en un nombre
Correcto. También es posible que desee echar un vistazo rápido a las interfaces en Go. Es decir, no tiene que declarar un tipo como implementación de una interfaz, solo tiene que implementar todos los métodos para esa interfaz.
Joey Adams
1
@ Notetoself-thinkofaname: con respecto a operadores adicionales, sí y no. Permite ==estar en un espacio de nombres diferente pero no permite anularlo. Tenga en cuenta que hay un único espacio de nombres ( Prelude) incluido de forma predeterminada, pero puede evitar cargarlo utilizando extensiones o importándolo explícitamente ( import Prelude ()no importará nada Preludey import qualified Prelude as Pno insertará símbolos en el espacio de nombres actual).
Maciej Piechotka
4

Permitir la sobrecarga de funciones, no puede hacer lo siguiente con parámetros opcionales (o si puede, no muy bien).

ejemplo trivial no asume ningún base.ToString() método

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
Binario Worrier
fuente
Para un lenguaje fuertemente tipado, sí. Para un lenguaje débilmente escrito, no. Puede hacer lo anterior con solo una función en un lenguaje de tipo débil.
spex
2

Siempre he preferido los parámetros predeterminados sobre la sobrecarga de funciones. Las funciones sobrecargadas generalmente solo llaman a una versión "predeterminada" con parámetros predeterminados. Porque escribir

int indexOf(char ch)
{
  return self.indexOf(ch, 0);
}

int indexOf(char ch, int fromIndex)
{
  // Do whatever
}

Cuando pude hacer:

int indexOf(char ch, int fromIndex=0)
{
  // Do whatever
}

Dicho esto, me doy cuenta de que a veces las funciones sobrecargadas hacen cosas diferentes, en lugar de simplemente llamar a otra variante con parámetros predeterminados ... pero en tal caso, no es una mala idea (de hecho, probablemente sea una buena idea) simplemente darle una nombre diferente.

(Además, los argumentos de palabras clave de estilo Python funcionan muy bien con los parámetros predeterminados).

mipadi
fuente
Bien, intentemos eso de nuevo, e intentaré tener sentido esta vez ... ¿Qué pasa Array Slice(int start, int length) {...}con sobrecargado Array Slice(int start) {return this.Slice(start, this.Count - start);}? Eso no se puede codificar con los parámetros predeterminados. ¿Crees que deberían recibir nombres diferentes? Si es así, ¿cómo los nombrarías?
Nota para uno mismo - piense en un nombre
Eso no aborda todos los usos de la sobrecarga.
MetalMikester
@MetalMikester: ¿Qué usos crees que extrañé?
mipadi
@mipadi Echa de menos cosas como indexOf(char ch)+ indexOf(Date dt)en una lista. También me gustan los valores predeterminados, pero no son intercambiables con la escritura estática.
Mark
2

Acabas de describir Java. O C #.

¿Por qué estás reinventando la rueda?

Asegúrate de que el tipo de retorno sea parte de la firma del método y sobrecargue el contenido de tu corazón, realmente limpia el código cuando no tienes que decirlo.

function getThisFirstWay(int type)
{ ... }
function getThisSecondWay(int type, double limit)
{ ... }
function getThisThirdWay(int type, String match)
{ ... }
Josh K
fuente
77
Hay una razón por la cual el tipo de retorno no es parte de la firma del método, o al menos no una parte que se pueda usar para la resolución de sobrecarga, en cualquier idioma que conozca. Cuando llama a una función como procedimiento, sin asignar el resultado a una variable o propiedad, ¿cómo se supone que el compilador debe determinar qué versión invocar si todos los demás argumentos son idénticos?
Mason Wheeler
@Mason: podría detectar heurísticamente el tipo de retorno esperado en función de lo que se espera devolver, sin embargo, no espero que se haga.
Josh K
1
Umm ... ¿cómo sabe tu heurística lo que se espera que se devuelva cuando no esperas ningún valor de retorno?
Mason Wheeler
1
La heurística de tipo de expectativa en realidad está en su lugar ... puedes hacer cosas como EnumX.Flag1 | Flag2 | Flag3. Sin embargo, no implementaré esto. Si lo hiciera y no se usara el tipo de retorno, buscaría un tipo de retorno de void.
Nota para uno mismo - piense en un nombre
1
@Mason: Esa es una buena pregunta, pero en ese caso buscaría una función nula (como se mencionó). Además, en teoría , podría elegir cualquiera de ellos, ya que todos realizarán la misma función, simplemente devolverán los datos en un formato diferente.
Josh K
2

Grrr ... no hay suficiente privilegio para comentar aún ...

@Mason Wheeler: Tenga en cuenta entonces a Ada, que se sobrecarga en el tipo de retorno. También mi lenguaje Felix lo hace también en algunos contextos, específicamente, cuando una función devuelve otra función y hay una llamada como:

f a b  // application is left assoc: (f a) b

El tipo de b se puede utilizar para la resolución de sobrecarga. También sobrecargas de C ++ en el tipo de retorno en algunas circunstancias:

int (*f)(int) = g; // choses g based on type, not just signature

De hecho, existen algoritmos para sobrecargar el tipo de retorno, utilizando la inferencia de tipos. En realidad no es tan difícil de hacer con una máquina, el problema es que a los humanos les resulta difícil. (Creo que el esquema se da en el Libro del Dragón, el algoritmo se llama algoritmo de ver y ver si recuerdo correctamente)

Yttrill
fuente
2

Caso de uso contra la sobrecarga de la función de implementación: 25 métodos con nombres similares que hacen las mismas cosas pero con conjuntos de argumentos completamente diferentes en una amplia variedad de patrones.

Caso de uso contra la no implementación de sobrecarga de funciones: 5 métodos con nombres similares con conjuntos de tipos muy similares en exactamente el mismo patrón.

Al final del día, no tengo ganas de leer los documentos para una API producida en cualquier caso.

Pero en un caso se trata de lo que los usuarios podrían hacer. En el otro caso, es lo que los usuarios deben hacer debido a una restricción de idioma. En mi opinión, es mejor al menos permitir la posibilidad de que los autores de programas sean lo suficientemente inteligentes como para sobrecargarse de manera sensata sin crear ambigüedad. Cuando golpeas sus manos y les quitas la opción, básicamente estás garantizando que sucederá la ambigüedad. Me gusta más confiar en que el usuario haga lo correcto que asumir que siempre harán lo incorrecto. En mi experiencia, el proteccionismo tiende a conducir a un comportamiento aún peor por parte de la comunidad de un idioma.

Erik Reppen
fuente
1

Elegí proporcionar sobrecarga ordinaria y clases de tipos de tipos múltiples en mi idioma Felix.

Considero que la sobrecarga (abierta) es esencial, especialmente en un lenguaje que tiene muchos tipos numéricos (Felix tiene todos los tipos numéricos de C). Sin embargo, a diferencia de C ++, que abusa de la sobrecarga al hacer que las plantillas dependan de él, el polimorfismo de Felix es paramétrico: es necesario sobrecargar las plantillas en C ++ porque las plantillas en C ++ están mal diseñadas.

Las clases de tipo también se proporcionan en Felix. Para aquellos que conocen C ++ pero no dominan a Haskell, ignoren a aquellos que lo describen como sobrecargado. No es remotamente como una sobrecarga, más bien, es como una especialización de plantilla: declara una plantilla que no implementa, luego proporciona implementaciones para casos particulares cuando los necesita. La tipificación es polimórfica paramétricamente, la implementación es por instanciación ad hoc pero no está destinada a ser sin restricciones: tiene que implementar la semántica prevista.

En Haskell (y C ++) no se puede establecer la semántica. En C ++, la idea de "Conceptos" es más o menos un intento de aproximar la semántica. En Félix, puede aproximar la intención con axiomas, reducciones, lemas y teoremas.

La principal y única ventaja de la sobrecarga (abierta) en un lenguaje bien basado en principios como Felix es que hace que sea más fácil recordar los nombres de las funciones de la biblioteca, tanto para el escritor del programa como para el revisor del código.

La principal desventaja de la sobrecarga es el complejo algoritmo requerido para implementarlo. Tampoco se adapta muy bien a la inferencia de tipos: aunque los dos no son completamente exclusivos, el algoritmo para hacer ambos es lo suficientemente complejo como para que el programador probablemente no pueda predecir los resultados.

En C ++, esto también es un problema porque tiene un algoritmo de coincidencia descuidado y también admite conversiones de tipo automáticas: en Félix "solucioné" este problema al requerir una coincidencia exacta y no conversiones de tipo automáticas.

Entonces, creo que tiene una opción: sobrecarga o inferencia de tipos. La inferencia es linda, pero también es muy difícil de implementar de manera que diagnostique conflictos adecuadamente. Ocaml, por ejemplo, le dice dónde detecta un conflicto, pero no de dónde dedujo el tipo esperado.

La sobrecarga no es mucho mejor, incluso si tiene un compilador de calidad que intenta decirle a todos los candidatos, puede ser difícil de leer si los candidatos son polimórficos, y aún peor si se trata de piratería de plantillas C ++.

Yttrill
fuente
Suena interesante. Me encantaría leer más, pero el enlace a los documentos en la página web de Felix está roto.
Nota para uno mismo - piense en un nombre
Sí, todo el sitio está actualmente en construcción (nuevamente), lo siento.
Yttrill
0

Todo se reduce al contexto, pero creo que la sobrecarga hace que una clase sea mucho más útil cuando estoy usando una escrita por otra persona. A menudo terminas con menos redundancia.

Morgan Herlocker
fuente
0

Si está buscando tener usuarios familiarizados con los lenguajes de la familia C, entonces sí debería, porque sus usuarios lo esperarán.

Conrad Frix
fuente