¿Qué hace que el operador de Scala sobrecargue "bien", pero que C ++ sea "malo"?

155

La sobrecarga de operadores en C ++ es considerada por muchos como una mala cosa (tm), y es un error que no se repite en lenguajes más nuevos. Ciertamente, fue una característica que se eliminó específicamente al diseñar Java.

Ahora que he empezado a leer sobre Scala, encuentro que tiene lo que se parece mucho a la sobrecarga del operador (aunque técnicamente no tiene sobrecarga del operador porque no tiene operadores, solo funciones). Sin embargo, no parece ser cualitativamente diferente a la sobrecarga del operador en C ++, donde, según recuerdo, los operadores se definen como funciones especiales.

Entonces, mi pregunta es ¿qué hace que la idea de definir "+" en Scala sea una mejor idea que en C ++?

skaffman
fuente
27
Ni C ++ ni Scala se definieron por consenso universal entre todos los programadores. No creo que haya ninguna contradicción entre el hecho de que algunas personas se quejan de C ++ y el hecho de que algunas personas no se quejan de Scala.
Steve Jessop
16
No hay nada malo en la sobrecarga del operador en C ++.
Puppy
55
Esto no es nada nuevo, pero la forma en que defiendo C ++ cuando la sobrecarga del operador y otras características "avanzadas" son cuestionadas es simple: C ++ nos da todo el poder para usarlo / abusarlo como mejor nos parezca. Siempre me ha gustado cómo se supone que somos competentes y autónomos y que no necesitamos que se tomen decisiones como esta.
Elliott
Scala fue diseñado como décadas después de c ++. Resulta que la persona detrás de esto es muy sabia en términos de lenguajes de programación. Tampoco tiene nada de malo, si te apegas a c ++ o Scala durante otros 100 años, ¡queda claro que probablemente ambos sean malos! Ser sesgado aparentemente está en nuestra naturaleza, pero podemos combatirlo, solo mirando la historia de la tecnología, todo se vuelve obsoleto.
Nader Ghanbari

Respuestas:

242

C ++ hereda verdaderos operadores azules de C. Con eso quiero decir que el "+" en 6 + 4 es muy especial. No puede, por ejemplo, obtener un puntero a esa función +.

Scala, por otro lado, no tiene operadores de esa manera. Simplemente tiene una gran flexibilidad para definir nombres de métodos más un poco de precedencia incorporada para símbolos que no son palabras. Así que técnicamente Scala no tiene sobrecarga del operador.

Como quiera llamarlo, la sobrecarga del operador no es inherentemente mala, incluso en C ++. El problema es cuando los malos programadores lo abusan. Pero, francamente, soy de la opinión de que eliminar la capacidad de los programadores de abusar de la sobrecarga del operador no pone una gota en el balde de arreglar todo lo que los programadores pueden abusar. La verdadera respuesta es la tutoría. http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

Sin embargo, existen diferencias entre la sobrecarga del operador de C ++ y la denominación de métodos flexibles de Scala que, en mi humilde opinión, hacen que Scala sea menos abusivo y más abusivo.

En C ++, la única forma de obtener la notación en corrección es utilizando operadores. De lo contrario, debe usar object.message (argumento) o puntero-> messsage (argumento) o función (argumento1, argumento2). Entonces, si desea un cierto estilo DSLish para su código, entonces hay presión para usar operadores.

En Scala puede obtener notación infija con cualquier mensaje enviado. "argumento de mensaje de objeto" está perfectamente bien, lo que significa que no necesita usar símbolos que no sean palabras solo para obtener la notación infija.

La sobrecarga del operador C ++ se limita esencialmente a los operadores C. Combinado con la limitación de que solo se pueden usar infix los operadores que presionan a las personas para que intenten mapear una amplia gama de conceptos no relacionados en relativamente pocos símbolos como "+" y ">>"

Scala permite una amplia gama de símbolos válidos que no son palabras como nombres de métodos. Por ejemplo, tengo un DSL Prolog-ish incrustado donde puedes escribir

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

Los símbolos: -,!,? Y & se definen como métodos comunes. Solo en C ++ sería válido, por lo que un intento de mapear este DSL en C ++ requeriría algunos símbolos que ya evocan conceptos muy diferentes.

Por supuesto, esto también abre Scala a otro tipo de abuso. En Scala puede nombrar un método $! & ^% Si lo desea.

Para otros lenguajes que, como Scala, son flexibles en el uso de nombres de funciones y métodos que no son de palabras, consulte Smalltalk donde, como Scala, cada "operador" es solo otro método y Haskell que permite al programador definir la precedencia y la fijación de nombres flexibles funciones

James Iry
fuente
La última vez que revisé, 3.operator + (5) funcionó. Estoy realmente sorprendido de que & (3.operator +) no lo haga.
Joshua
podría, por ejemplo, hacer valer (female ("jane")) en c ++. Eso no sería confuso en absoluto: asiente de nuevo a la publicación de james-iry sobre que no es que el operador + sea una mala cosa, pero los programadores estúpidos sí lo son.
pm100
1
@Joshua int main() {return (3).operator+(5);}resultados enerror: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01
Eso es un montón de basura arrogante: "la sobrecarga del operador no es inherentemente mala, incluso en C ++. El problema es cuando los malos programadores abusan de ella". Si se puede abusar fácilmente de algo con un beneficio bastante pequeño al usarlo, el resultado general es que el siguiente tipo que mantenga su código perderá productividad al descifrar las partes más extrañas de su código. De lo contrario: respuesta muy informativa y bien escrita.
Jukka Dahlbom
@JukkaDahlbom La existencia de punteros inteligentes hace que el beneficio sea grande por sí solo. Y luego tienes lambdas, tipos de números definidos por el usuario, tipos de intervalos ...
Alexey Romanov
66

La sobrecarga del operador en C ++ es considerada por muchos como una mala cosa (tm)

Solo por los ignorantes. Es absolutamente necesario en un lenguaje como C ++, y es notable que otros lenguajes que comenzaron con una visión "purista", lo hayan agregado una vez que sus diseñadores descubrieron lo necesario que es.


fuente
30
De hecho, estoy de acuerdo con Neil. La sobrecarga del operador es esencial si desea presentar variables / constantes / objetos / instancias como entidades algebraicas ... y hacer que las personas entiendan sus interacciones de manera matemática, que debería ser cómo funciona la programación en mi humilde opinión.
Massa
16
+1, la sobrecarga del operador en C ++ es buena. Por ejemplo, hace que las matemáticas vectoriales sean mucho más limpias. Al igual que con muchas características de C ++, debe ejercer el poder con cuidado.
John Smith
77
@Kristo Porque C ++ usa valores que deben asignarse y copiarse. Es necesario tener control sobre eso, por lo que debe poder especificar el operador de asignación para un tipo determinado, como mínimo.
77
@Kristo: porque una intención de C ++ es permitir que los tipos definidos por el usuario hagan todo lo que hacen los tipos incorporados (aunque se los trate de manera diferente en algunos contextos, como las conversiones implícitas). Si desea implementar un número entero de 27 bits, puede hacerlo, y usarlo será como usar int. Sin la sobrecarga del operador, no sería posible utilizar UDT con la misma sintaxis que los tipos incorporados y, por lo tanto, el lenguaje resultante no sería "como C ++" en este sentido.
Steve Jessop
8
"de esa manera se encuentra la locura" - ¡peor aún, de esa manera se encuentra std :: vector <bool>!
Steve Jessop
42

La sobrecarga del operador nunca se pensó universalmente como una mala idea en C ++, solo se pensó que el abuso de la sobrecarga del operador era una mala idea. Uno realmente no necesita la sobrecarga del operador en un idioma ya que de todos modos se pueden simular con llamadas a funciones más detalladas. Evitar la sobrecarga de operadores en Java hizo que la implementación y especificación de Java fuera un poco más simple y obligó a los programadores a no abusar de los operadores. Ha habido cierto debate en la comunidad Java sobre la introducción de la sobrecarga del operador.

Las ventajas y desventajas de la sobrecarga del operador en Scala son las mismas que en C ++: puede escribir un código más natural si usa la sobrecarga del operador de manera adecuada, y un código más críptico y ofuscado si no lo hace.

FYI: Los operadores no se definen como funciones especiales en C ++, se comportan como cualquier otra función, aunque existen algunas diferencias en la búsqueda de nombres, si necesitan ser funciones miembro y el hecho de que se pueden llamar de dos maneras: 1 ) sintaxis del operador, y 2) sintaxis del operador-función-id.

Faisal Vali
fuente
"Uno realmente no necesita la sobrecarga del operador en un idioma, ya que de todos modos se pueden simular con llamadas a funciones más detalladas". Uno ni siquiera necesita operadores bajo esa lógica. ¿Por qué no solo usar add(2, multiply(5, 3))?
Joe Z.
Es más un caso de coincidencia con las notaciones habituales utilizadas. Considere matemáticos y físicos, pueden entender y usar una biblioteca C ++ que proporciona sobrecargas de los operadores mucho más fácilmente. Prefieren concentrarse en la ecuación que en el lenguaje de programación.
Phil Wright
19

Este artículo, " El legado positivo de C ++ y Java ", responde a su pregunta directamente.

"C ++ tiene asignación de pila y asignación de montón y debe sobrecargar a sus operadores para manejar todas las situaciones y no causar pérdidas de memoria. De hecho, es difícil. Java, sin embargo, tiene un mecanismo de asignación de almacenamiento único y un recolector de basura, lo que hace que la sobrecarga del operador sea trivial". ..

Java erróneamente (según el autor) omitió la sobrecarga del operador porque era complicado en C ++, pero olvidó por qué (o no se dio cuenta de que no se aplicaba a Java).

Afortunadamente, los lenguajes de nivel superior como Scala ofrecen opciones a los desarrolladores, mientras se ejecutan en la misma JVM.

jmanning2k
fuente
14
Eckel es la única fuente que he visto por la idea de que la sobrecarga del operador se eliminó de Java debido a complicaciones en C ++ y no dice cuál es su fuente. Lo descontaría. Todas las otras fuentes que tengo dicen que fue abandonada debido a posibles abusos. Ver gotw.ca/publications/c_family_interview.htm y newt.com/wohler/articles/james-gosling-ramblings-1.html . Solo busque en la página "sobrecarga del operador".
James Iry el
9

No hay nada malo con la sobrecarga del operador. De hecho, hay algo malo en no tener una sobrecarga del operador para los tipos numéricos. (Eche un vistazo a algunos códigos Java que usan BigInteger y BigDecimal).

Sin embargo, C ++ tiene la tradición de abusar de la función. Un ejemplo citado a menudo es que los operadores de desplazamiento de bits están sobrecargados para hacer E / S.

dan04
fuente
Los operadores << y >> indican visualmente la forma de transferencia, están destinados a hacer E / S, no es abuso, es de una biblioteca estándar y práctica. Solo mira "cin >> algo", ¿qué va a dónde? De cin, a algo, obviamente.
maní
77
@peenut: Pero su uso original fue un poco cambiante. La "biblioteca estándar" utiliza el operador de una manera que se mete completamente con la definición original.
Joe Z.
1
Estoy seguro de que he leído en alguna parte que Bjarne Stroustrup (creador de C ++) experimentó con el uso en =lugar de <<y >>en los primeros días de C ++, pero tuvo problemas ya que no tenía la prioridad de operador correcta (es decir, busca argumentos a la izquierda o derecha primero). Así que sus manos estaban un poco atadas sobre lo que podía usar.
Phil Wright
8

En general no es algo malo.
Nuevos lenguajes como C # también tienen sobrecarga del operador.

Es el abuso de la sobrecarga del operador lo que es malo.

Pero también hay problemas con la sobrecarga del operador como se define en C ++. Debido a que los operadores sobrecargados son simplemente azúcar sintáctica para las llamadas a métodos, se comportan igual que los métodos. Por otro lado, los operadores integrados normales no se comportan como los métodos. Estas inconsistencias pueden causar problemas.

De la parte superior de mi cabeza operadores ||y &&.
Las versiones integradas de estos son operadores de acceso directo. Esto no es cierto para las versiones sobrecargadas y ha causado algunos problemas.

El hecho de que + - * / todos devuelven el mismo tipo en el que operan (después de la promoción del operador)
Las versiones sobrecargadas pueden devolver cualquier cosa (Aquí es donde se establece el abuso, si sus operadores comienzan a devolver algún tipo de árbitro que el usuario no esperaba las cosas van cuesta abajo).

Martin York
fuente
8

La sobrecarga del operador no es algo que realmente "necesite" muy a menudo, pero cuando usa Java, si llega a un punto en el que realmente lo necesita, tendrá ganas de arrancarse las uñas para tener una excusa para dejar de escribir. .

¿Ese código que acabas de encontrar se desborda mucho? Sí, vas a tener que volver a escribir todo para que funcione con BigInteger. No hay nada más frustrante que tener que reinventar la rueda solo para cambiar el tipo de variable.


fuente
6

Guy Steele argumentó que la sobrecarga del operador también debería estar en Java, en su discurso principal "Cultivando un lenguaje": hay un video y una transcripción del mismo, y es realmente un discurso increíble. Te preguntarás de qué está hablando durante las primeras páginas, pero si sigues leyendo, verás el punto y lograrás la iluminación. Y el hecho mismo de que pudiera pronunciar ese discurso también es sorprendente.

Al mismo tiempo, esta charla inspiró mucha investigación fundamental, probablemente incluyendo a Scala, es uno de esos documentos que todos deberían leer para trabajar en el campo.

Volviendo al punto, sus ejemplos son principalmente sobre clases numéricas (como BigInteger y algunas cosas más extrañas), pero eso no es esencial.

Sin embargo, es cierto que el mal uso de la sobrecarga del operador puede conducir a resultados terribles, y que incluso los usos adecuados pueden complicar las cosas, si intenta leer el código sin estudiar un poco las bibliotecas que utiliza. ¿Pero es una buena idea? OTOH, ¿no deberían estas bibliotecas tratar de incluir una hoja de trucos para los operadores?

Blaisorblade
fuente
4

Creo que CADA respuesta se perdió esto. En C ++ puede sobrecargar a los operadores todo lo que quiera, pero no puede afectar la precedencia con la que se evalúan. Scala no tiene este problema, IIRC.

En cuanto a que es una mala idea, además de los problemas de precedencia, la gente tiene significados realmente tontos para los operadores, y rara vez ayuda a la legibilidad. Las bibliotecas Scala son especialmente malas para esto, símbolos tontos que debes memorizar cada vez, con los mantenedores de la biblioteca metiendo la cabeza en la arena diciendo: 'solo necesitas aprenderlo una vez'. Genial, ahora necesito aprender la sintaxis críptica de un autor "inteligente" * la cantidad de bibliotecas que me interesa usar. No sería tan malo si existiera una convención de SIEMPRE de suministrar una versión alfabetizada de los operadores.

Saem
fuente
1
Scala también tiene prioridad de operador fija, ¿no?
skaffman
Creo que sí, pero es mucho más plano. Más concretamente, Scala tiene menos período de operadores. +, -, * son métodos, no operadores, IIRC. Es por eso que 2 + 3 * 2, no es 8, es 10.
Saem
77
Scala tiene un sistema de precedencia basado en el primer carácter del símbolo. scala> 2 + 3 * 2 res0: Int = 8
James Iry
3

La sobrecarga del operador no fue una invención de C ++: vino de Algol IIRC e incluso Gosling no afirma que sea una mala idea en general.

Nemanja Trifunovic
fuente
Claro, pero fue en su encarnación C ++ que ganó un aire general de mala reputación.
skaffman
55
¿Qué quiere decir con "aire general de mala reputación"? La mayoría de las personas que conozco usan lenguajes que admiten la sobrecarga del operador (C ++, C #) y nunca he escuchado ninguna queja.
Nemanja Trifunovic
Estoy hablando de mi experiencia pasada con C ++ anterior a ANSI, y ciertamente recuerdo una aversión común hacia ellos. Quizás la situación mejoró con ANSI C ++, o la gente aprendió a no abusar de ella.
skaffman
1
Hablando como alguien que ha estado usando C ++ desde los primeros días (mediados de los 80), puedo asegurarle que la introducción de la norma ISO no tuvo ningún efecto sobre los prejuicios de las personas con respecto a la sobrecarga del operador.
3

Lo único que se sabe mal en C ++ es la falta de la capacidad de sobrecargar [] = como un operador separado. Esto podría ser difícil de implementar en un compilador de C ++ por lo que probablemente no sea una razón obvia pero valga la pena.

Joshua
fuente
2

Como las otras respuestas han señalado; La sobrecarga del operador en sí no es necesariamente mala. Lo que es malo cuando se usa de manera que hace que el código resultante no sea obvio. En general, cuando los usa, debe hacer que hagan lo menos sorprendente (tener una división de operador + do causaría problemas para el uso de una clase racional) o como dice Scott Meyers:

Los clientes ya saben cómo se comportan los tipos como int, por lo que debe esforzarse para que sus tipos se comporten de la misma manera siempre que sea razonable ... En caso de duda, haga lo que hacen los ints . (Del artículo 18 de C ++ 3rd Edition efectivo)

Ahora, algunas personas han llevado la sobrecarga del operador al extremo con cosas como boost :: spirit . En este nivel, no tiene idea de cómo se implementa, pero crea una sintaxis interesante para obtener lo que desea hacer. No estoy seguro de si esto es bueno o malo. Parece agradable, pero no lo he usado.

Matt Price
fuente
No estoy discutiendo a favor o en contra de la sobrecarga de operadores aquí, no estoy buscando personas para justificarlos.
skaffman
Sprint no se acerca al peor ejemplo que he encontrado: ¡deberías ver lo que hace la biblioteca de la base de datos RogueWave!
Estoy de acuerdo en que Spirit hace un mal uso de los operadores, pero realmente no puedo pensar en una mejor manera de hacerlo.
Zifre
1
No creo que el espíritu esté abusando de los operadores, pero lo está presionando. Estoy de acuerdo en que realmente no hay otra forma de hacerlo. Básicamente crea un DSL dentro de la sintaxis de C ++. Muy lejos de lo que C ++ fue diseñado para hacer. Sí, hay ejemplos mucho peores :) En general, los uso cuando corresponde. Principalmente solo los operadores de transmisión para una depuración / registro más fácil. E incluso allí es solo el azúcar que reenvía a un método implementado en la clase.
Matt Price
1
Es una pregunta de sabor; pero las bibliotecas de combinador de analizadores, en lenguajes funcionales, sobrecargan a los operadores de una manera muy similar a Spirit, y nadie discute en contra de eso. Hay muchas razones técnicas por las cuales son mejores: Google para "lenguajes específicos de dominio incrustados" para encontrar muchos documentos que lo explican desde un punto de vista general, y Google para "combinador de analizador de escala" para ejemplos prácticos en este caso. Es cierto que en los lenguajes funcionales la sintaxis resultante suele ser más agradable; por ejemplo, no es necesario cambiar el significado de >> para concatenar analizadores sintácticos.
Blaisorblade
2

Nunca he visto un artículo que afirme que la sobrecarga del operador de C ++ es mala.

Los operadores definibles por el usuario permiten un mayor nivel de expresividad y usabilidad para los usuarios del idioma.

Paul Nathan
fuente
1

Sin embargo, no parece ser cualitativamente diferente a la sobrecarga del operador en C ++, donde, según recuerdo, los operadores se definen como funciones especiales.

AFAIK, No hay nada especial en las funciones del operador en comparación con las funciones miembro "normales". Por supuesto, solo tiene un cierto conjunto de operadores que puede sobrecargar, pero eso no los hace muy especiales.

John Smith
fuente