it-swarm-es.com

¿Sobrecarga de funciones? si o no

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[++|#] antecedentes.

¿Cuáles son los argumentos más convincentes a favor y en contra incluyendo la sobrecarga de funciones en un idioma?


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

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 O-O, 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?

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 funciones miembro. A.foo() puede llamar a una función completamente diferente de B.foo(), pero ambas funciones se denominan foo. Está presente en los operadores, como + hace cosas diferentes cuando se aplica a números enteros y de 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" comunes de estilo LISP, en los que la función exacta llamada depende de dos tipos de datos. Si no ha programado en el Sistema de objetos LISP común, 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 las 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.

24
David Thornley

Recomiendo al menos estar al tanto de las clases de tipos en Haskell. Las clases de tipos se crearon para ser un enfoque disciplinado para 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 tiene que encontrar nombres originales para todas sus clases de tipos (como en Haskell, tiene el bastante abstracto Monad, Functor, Applicative también como el más simple y reconocible Eq, Num y 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 querías un == operador que acepta 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 para +, ya que agrega un Word8 a un Int realmente es algo sensato que hacer en algunos contextos.

8
Joey Adams

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

el ejemplo trivial supone que nobase.ToString()método

string ToString(int i) {}
string ToString(double d) {}
string ToString(DateTime d) {}
...
4
Binary Worrier

Acabas de describir Java. O C #.

¿Por qué estás reinventando la rueda?

Asegúrese de que el tipo de retorno sea parte de la firma del método y Sobrecarga 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)
{ ... }
2
Josh K

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 un bueno idea) solo darle un nombre diferente.

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

2
mipadi

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 permitir al menos 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.

2
Erik Reppen

Grrr ... no hay suficiente privilegio para comentar todavía ...

@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)

2
Yttrill

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 pretende 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 aproximadamente un intento de aproximar la semántica. En Felix, puede aproximar la intención con axiomas, reducciones, lemas y teoremas.

La ventaja principal y solo de la sobrecarga (abierta) en un lenguaje de 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 algoritmo complejo 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++.

1
Yttrill

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.

0
Morgan Herlocker

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

0
Conrad Frix