¿Por qué los operadores definidos por el usuario no son más comunes?

94

Una característica que extraño de los lenguajes funcionales es la idea de que los operadores son solo funciones, por lo que agregar un operador personalizado suele ser tan simple como agregar una función. Muchos lenguajes de procedimiento permiten sobrecargas de operadores, por lo que, en cierto sentido, los operadores siguen siendo funciones (esto es muy cierto en D, donde el operador se pasa como una cadena en un parámetro de plantilla).

Parece que donde se permite la sobrecarga del operador, a menudo es trivial agregar operadores personalizados adicionales. Encontré esta publicación de blog , que argumenta que los operadores personalizados no funcionan bien con la notación infija debido a las reglas de precedencia, pero el autor da varias soluciones a este problema.

Miré a mi alrededor y no pude encontrar ningún lenguaje de procedimiento que admitiera operadores personalizados en el idioma. Hay hacks (como macros en C ++), pero eso no es lo mismo que el soporte de idiomas.

Dado que esta característica es bastante trivial de implementar, ¿por qué no es más común?

Entiendo que puede conducir a un código feo, pero eso no ha impedido que los diseñadores de idiomas agreguen características útiles que pueden ser fácilmente abusadas (macros, operador ternario, punteros inseguros).

Casos de uso reales:

  • Implementar operadores faltantes (por ejemplo, Lua no tiene operadores bit a bit)
  • Mimic D's ~(concatenación de matriz)
  • DSL
  • Úselo |como azúcar de sintaxis estilo tubería Unix (usando corutinas / generadores)

También estoy interesado en lenguajes que hacen que los operadores personalizados, pero estoy más interesado en por qué se ha excluido. Pensé en bifurcar un lenguaje de secuencias de comandos para agregar operadores definidos por el usuario, pero me detuve cuando me di cuenta de que no lo había visto en ningún lado, por lo que probablemente haya una buena razón por la que los diseñadores de idiomas más inteligentes que yo no lo han permitido.

beatgammit
fuente
44
Hay una discusión de Reddit sobre esta cuestión.
Dinámico
2
@dimatura: no sé mucho acerca de R, pero sus operadores personalizados aún no parecen muy flexibles (por ejemplo, no hay una forma adecuada de definir la fijación, el alcance, la sobrecarga, etc.), lo que explica por qué no se usan mucho. Eso es diferente en otros idiomas, sobre todo en Haskell que utiliza operadores infijos personalizados un gran lote . Otro ejemplo de lenguaje de procedimiento con soporte de infijo razonablemente personalizado es Nimrod , y por supuesto Perl también lo permite .
Leftaroundabout
44
Existe la otra opción, Lisp en realidad no tiene diferencia entre operadores y funciones.
BeardedO
44
Aún no se menciona Prolog, otro lenguaje en el que los operadores son simplemente azúcar sintáctica para funciones (sí, incluso matemáticas) y que le permite definir operadores personalizados con precedencia personalizada.
David Cowden
2
@BeardedO, no hay operadores infijos en Lisp. Una vez que los presente, tendrá que lidiar con todos los problemas con prioridad y tal.
SK-logic

Respuestas:

134

Hay dos escuelas de pensamiento diametralmente opuestas en el diseño del lenguaje de programación. Una es que los programadores escriben mejor código con menos restricciones, y la otra es que escriben mejor código con más restricciones. En mi opinión, la realidad es que los programadores experimentados prosperan con menos restricciones, pero esas restricciones pueden beneficiar la calidad del código de los principiantes.

Los operadores definidos por el usuario pueden crear un código muy elegante en manos experimentadas y un código completamente horrible para un principiante. Entonces, si su idioma los incluye o no, depende de la escuela de pensamiento de su diseñador de idiomas.

Karl Bielefeldt
fuente
23
En las dos escuelas de pensamiento, plus.google.com/110981030061712822816/posts/KaSKeg4vQtz también +1 por tener la respuesta más directa (y probablemente la más verdadera) a la pregunta de por qué los diseñadores de idiomas eligen uno frente al otro. También diría que, en base a su tesis, podría extrapolar que menos idiomas lo permiten porque una porción más pequeña de desarrolladores son altamente calificados que de otra manera (el porcentaje superior de cualquier cosa siempre será minoritario por definición)
Jimmy Hoffa
12
Creo que esta respuesta es vaga y solo marginalmente relacionada con la pregunta. (También creo que su caracterización es incorrecta, ya que contradice mi experiencia, pero eso no es importante.)
Konrad Rudolph
1
Como dijo @KonradRudolph, esto realmente no responde la pregunta. Tome un lenguaje como Prolog que le permite hacer lo que quiera con los operadores, incluida la definición de su precedencia cuando se coloca infijo o prefijo. No creo que el hecho de que pueda escribir operadores personalizados tenga algo que ver con el nivel de habilidad del público objetivo, sino más bien porque el objetivo de Prolog es leer lo más lógicamente posible. La inclusión de operadores personalizados le permite escribir programas que se leen de manera muy lógica (después de todo, un programa de prólogo es solo un conjunto de declaraciones lógicas).
David Cowden
¿Cómo extrañé esa diatriba de Steve? Oh hombre, marcado como favorito
George Mauer
¿En qué filosofía caería si creo que es más probable que los programadores escriban el mejor código si los lenguajes les dan las herramientas para decir cuándo el compilador debe hacer varias inferencias (por ejemplo, argumentos de boxeo automático a un Formatmétodo) y cuándo debe rechazar ( por ejemplo, argumentos de boxeo automático para ReferenceEquals). Cuanto mayor sea el lenguaje de habilidad que los programadores puedan decir cuando ciertas inferencias serían inapropiadas, más seguro puede ofrecer inferencias convenientes cuando sea apropiado.
supercat
83

Dada la opción entre concatenar matrices con ~ o con "myArray.Concat (secondArray)", probablemente preferiría la última. ¿Por qué? Porque ~ es un personaje completamente sin sentido que solo tiene su significado, el de concatenación de matriz, dado en el proyecto específico donde fue escrito.

Básicamente, como dijiste, los operadores no son diferentes de los métodos. Pero si bien los métodos pueden recibir nombres legibles y comprensibles que contribuyan a la comprensión del flujo de código, los operadores son opacos y situacionales.

Es por eso que tampoco me gusta el .operador de PHP (concatenación de cadenas) o la mayoría de los operadores en Haskell u OCaml, aunque en este caso, están surgiendo algunos estándares universalmente aceptados para lenguajes funcionales.

Avner Shahar-Kashtan
fuente
27
Lo mismo podría decirse de todos los operadores. Lo que los hace buenos, y también puede hacer que los operadores definidos por el usuario sean buenos (si se definen juiciosamente), es esto: a pesar de que el carácter utilizado para ellos es solo un carácter, el uso generalizado y posiblemente las propiedades mnemónicas hacen que su significado en el código fuente sea inmediato Obvio para aquellos que están familiarizados con él. Entonces su respuesta tal como está no es tan convincente en mi humilde opinión.
33
Como mencioné al final, algunos operadores tienen reconocimiento universal (como los símbolos aritméticos estándar, los cambios bit a bit, AND / OR, y así sucesivamente), por lo que su brevedad supera su opacidad. Pero poder definir operadores arbitrarios parece mantener lo peor de ambos mundos.
Avner Shahar-Kashtan
11
¿Entonces también está a favor de prohibir, por ejemplo, nombres de una sola letra a nivel de idioma? En términos más generales, ¿no permite nada en el lenguaje que parezca hacer más daño que bien? Es un enfoque válido para el diseño del lenguaje, pero no el único, por lo que no creo que pueda presuponerlo.
99
No estoy de acuerdo con que los operadores personalizados estén "agregando" una función, están eliminando una restricción. Los operadores generalmente son solo funciones, por lo que en lugar de una tabla de símbolos estática para operadores, se podría usar una dinámica, dependiente del contexto. Me imagino que esto es cómo se manejan las sobrecargas de operadores, ya +y <<desde luego no se definen en Object(me sale "no puede competir con el operador + en ..." cuando se hace eso en una clase desnuda en C ++).
beatgammit
10
Lo que lleva mucho tiempo darse cuenta es que la codificación generalmente no se trata de hacer que una computadora haga algo por usted, se trata de generar un documento que tanto las personas como las computadoras puedan leer, siendo las personas un poco más importantes que las computadoras. Esta respuesta es exactamente correcta, si la siguiente persona (o usted en 2 años) tiene que pasar incluso 10 segundos tratando de averiguar qué significa ~, también podría haber pasado los 10 segundos escribiendo una llamada al método.
Bill K
71

Dado que esta característica es bastante trivial de implementar, ¿por qué no es más común?

Tu premisa está mal. Es no “bastante trivial para poner en práctica”. De hecho, trae una bolsa de problemas.

Echemos un vistazo a las "soluciones" sugeridas en la publicación:

  • Sin precedencia . El propio autor dice: "No usar reglas de precedencia simplemente no es una opción".
  • Análisis semántico consciente . Como dice el artículo, esto requeriría que el compilador tenga mucho conocimiento semántico. El artículo en realidad no ofrece una solución para esto y déjame decirte que esto simplemente no es trivial. Los compiladores están diseñados como una compensación entre potencia y complejidad. En particular, el autor menciona un paso de análisis previo para recopilar la información relevante, pero el análisis previo es ineficiente y los compiladores se esfuerzan mucho para minimizar los pases de análisis.
  • No hay operadores infix personalizados . Bueno, eso no es una solución.
  • Solución híbrida . Esta solución conlleva muchas (pero no todas) las desventajas del análisis semántico consciente. En particular, dado que el compilador tiene que tratar los tokens desconocidos como potenciales operadores personalizados, a menudo no puede generar mensajes de error significativos. También puede requerir la definición de dicho operador para proceder con el análisis (para recopilar información de tipo, etc.), una vez más necesita un pase de análisis adicional.

Con todo, esta es una característica costosa de implementar, tanto en términos de complejidad del analizador como en términos de rendimiento, y no está claro que traiga muchos beneficios. Claro, hay algunos beneficios en la capacidad de definir nuevos operadores, pero incluso esos son polémicos (solo mire las otras respuestas argumentando que tener nuevos operadores no es algo bueno).

Konrad Rudolph
fuente
2
Gracias por la voz de la cordura. Mi experiencia sugiere que las personas que descartan las cosas como triviales son expertos o felizmente ignorantes, y más a menudo en la última categoría: x
Matthieu M.
14
Cada uno de estos problemas se ha resuelto en idiomas que han existido durante 20 años ...
Philip JF
11
Y, sin embargo, es excepcionalmente trivial de implementar. Olvídate de todos los algoritmos de análisis de libros de dragones, es un siglo XXI y es hora de seguir adelante. Incluso extender una sintaxis de lenguaje en sí es fácil, y agregar operadores de una precedencia dada es trivial. Echa un vistazo, por ejemplo, al analizador Haskell. Es mucho, mucho más simple que los analizadores para los lenguajes hinchados "convencionales".
SK-logic
3
@ SK-logic Su afirmación general no me convence. Sí, el análisis ha continuado. No, la implementación de precedencia de operadores arbitrarios dependiendo del tipo no es "trivial". Producir buenos mensajes de error con un contexto limitado no es "trivial". Producir analizadores eficientes con lenguajes que requieren múltiples pases para transformar no es factible.
Konrad Rudolph
66
En Haskell, el análisis es "fácil" (en comparación con la mayoría de los idiomas). La precedencia es independiente del contexto. La parte que le da mensajes de error físicos está relacionada con la clase de tipos y las características avanzadas del sistema de tipos, no con los operadores definidos por el usuario. Es la sobrecarga, no la definición del usuario, ese es el problema difícil.
Philip JF
25

Ignoremos por el momento todo el argumento de "los operadores se abusan para perjudicar la legibilidad" y centrémonos en las implicaciones del diseño del lenguaje.

Los operadores Infix tienen más problemas que las simples reglas de precedencia (aunque para ser contundente, el enlace al que hace referencia trivializa el impacto de esa decisión de diseño). Una es la resolución de conflictos: ¿qué sucede cuando define a.operator+(b)y b.operator+(a)? Preferir uno sobre el otro lleva a romper la propiedad conmutativa esperada de ese operador. Lanzar un error puede llevar a que los módulos que de otro modo funcionarían se rompan una vez juntos. ¿Qué sucede cuando comienzas a arrojar tipos derivados a la mezcla?

El hecho es que los operadores no son solo funciones. Las funciones son independientes o pertenecen a su clase, lo que proporciona una preferencia clara sobre qué parámetro (si corresponde) posee el envío polimórfico.

Y eso ignora los diversos problemas de empaque y resolución que surgen de los operadores. La razón por la cual los diseñadores de idiomas (en general) limitan la definición del operador infijo es porque crea una pila de problemas para el idioma al tiempo que proporciona un beneficio discutible.

Y, francamente, porque son no triviales de implementar.

revs Telastyn
fuente
1
Creo que esta es la razón por la que Java no los incluye, pero los lenguajes que los tienen tienen reglas claras de precedencia. Creo que es similar a la multiplicación de matrices. No es conmutativo, por lo que debe tener en cuenta cuando usa matrices. Creo que lo mismo se aplica cuando se trata de clases, pero sí, estoy de acuerdo en que tener un no conmutativo +es malo. Pero, ¿es esto realmente un argumento en contra de los operadores definidos por el usuario? Parece un argumento en contra de la sobrecarga del operador en general.
beatgammit
1
@tjameson: Sí, los idiomas tienen reglas claras para la precedencia, para las matemáticas . Es probable que las reglas de precedencia para operaciones matemáticas no se apliquen realmente si los operadores están sobrecargados por cosas como boost::spirit. Tan pronto como permita que los operadores definidos por el usuario empeoren, ya que no hay una buena manera de definir bien la precedencia para las matemáticas. He escrito un poco sobre eso en el contexto de un lenguaje que busca específicamente abordar problemas con operadores definidos arbitrariamente.
Telastyn
22
Llamo mierda, señor. Hay vida fuera del OO-ghetto, y sus funciones no pertenecen necesariamente a ningún objeto, por lo tanto, encontrar la función correcta es exactamente lo mismo que encontrar el operador correcto.
Matthieu M.
1
@matthieuM. - Ciertamente. Las reglas de propiedad y despacho son más uniformes en aquellos idiomas que no admiten funciones de miembro. En este punto, sin embargo, la pregunta original se convierte en '¿por qué los lenguajes que no son OO son más comunes?' que es un juego completamente diferente.
Telastyn
11
¿Has visto cómo Haskell hace operadores personalizados? Funcionan exactamente como las funciones normales, excepto que también tienen una precedencia asociada. (De hecho, también pueden funcionar las funciones normales, por lo que incluso allí no difieren realmente). Básicamente, los operadores tienen infijo por defecto y los nombres son prefijos, pero esa es la única diferencia.
Tikhon Jelvis
19

Creo que se sorprendería de la frecuencia con la que se implementan sobrecargas del operador de alguna forma. Pero no se usan comúnmente en muchas comunidades.

¿Por qué usar ~ para concatenar a una matriz? ¿Por qué no usar << como lo hace Ruby ? Debido a que los programadores con los que trabaja probablemente no sean programadores de Ruby. O los programadores D. Entonces, ¿qué hacen cuando se encuentran con su código? Tienen que ir a buscar lo que significa el símbolo.

Solía ​​trabajar con un muy buen desarrollador de C # que también tenía gusto por los lenguajes funcionales. De la nada, comenzó a presentar mónadas a C # a través de métodos de extensión y utilizando la terminología estándar de mónada. Nadie podía discutir que parte de su código era más terser e incluso más legible una vez que se sabía lo que significaba, pero sí significaba que todos tenían que aprender terminología de mónada antes de que el código tuviera sentido .

Bastante justo, ¿crees? Era solo un pequeño equipo. Personalmente, no estoy de acuerdo. Cada nuevo desarrollador estaba destinado a confundirse con esta terminología. ¿No tenemos suficientes problemas para aprender un nuevo dominio?

Por otro lado, usaré con gusto el ??operador en C # porque espero que otros desarrolladores de C # sepan qué es, pero no lo sobrecargaría en un lenguaje que no lo admite de forma predeterminada.

pdr
fuente
No entiendo su ejemplo Double.NaN, este comportamiento es parte de la especificación de coma flotante y es compatible con todos los idiomas que he usado. ¿Está diciendo que los operadores personalizados no son compatibles porque confundiría a los desarrolladores que no conocen la función? Eso suena como el mismo argumento en contra del uso del operador ternario o su ??ejemplo.
beatgammit
@tjameson: Tienes razón, en realidad, eso fue un poco tangencial al punto que estaba tratando de hacer, y no particularmente bien escrito. Pensé en el ?? ejemplo como estaba escribiendo eso y prefiero ese ejemplo. Eliminando el párrafo double.NaN.
pdr
66
Creo que su punto de "cada nuevo desarrollador estaba destinado a confundirse con esta terminología" es un poco deslucido, preocuparse por la curva de aprendizaje de los nuevos desarrolladores en su base de código es para mí lo mismo que preocuparse por la curva de aprendizaje de los nuevos desarrolladores. nueva versión de .NET. Creo que lo más importante es cuando los desarrolladores lo aprenden (nuevo .NET o su base de código), ¿tiene sentido y muestra valor? Si es así, la curva de aprendizaje lo vale, porque cada desarrollador solo necesita aprenderlo una vez, aunque el código debe ser manejado incontables veces, por lo que si mejora el código es un costo menor.
Jimmy Hoffa
77
@JimmyHoffa Es un costo recurrente. Se paga por adelantado por cada nuevo desarrollador y también afecta a los desarrolladores existentes porque tienen que soportarlo. También hay un costo de documentación y un elemento de riesgo: hoy se siente lo suficientemente seguro, pero 30 años después, todos habrán avanzado, el idioma y la aplicación ahora son "heredados", la documentación es una enorme pila de vapor y algún pobre imbécil se quedará rasgando su cabello ante la brillantez de los programadores que eran demasiado vagos para escribir ".concat ()". ¿El valor esperado es suficiente para compensar los costos?
Sylverdrag
3
Además, el argumento "Cada nuevo desarrollador estaba destinado a confundirse con esta terminología. ¿No tenemos suficientes problemas para aprender un nuevo dominio?" podría haberse aplicado a OOP en los años 80, programación estructurada antes de eso, o IoC hace 10 años. Es hora de dejar de usar este argumento falaz.
Mauricio Scheffer
11

Se me ocurren algunas razones:

  • No son triviales de implementar : permitir operadores personalizados arbitrarios puede hacer que su compilador sea mucho más complejo, especialmente si permite reglas de prioridad, fijeza y aridad definidas por el usuario. Si la simplicidad es una virtud, la sobrecarga del operador lo aleja del buen diseño del lenguaje.
  • Son abusados , principalmente por codificadores que piensan que es "genial" redefinir operadores y comenzar a redefinirlos para todo tipo de clases personalizadas. En poco tiempo, su código está plagado de una carga de símbolos personalizados que nadie más puede leer o comprender porque los operadores no siguen las reglas convencionales bien entendidas. No compro el argumento "DSL", a menos que su DSL sea un subconjunto de matemáticas :-)
  • Ellos duelen legibilidad y mantenibilidad - si los operadores se reemplazan regularmente, puede llegar a ser difícil de detectar cuando se está utilizando este servicio, y codificadores están obligados a pedir continuamente a sí mismos lo que un operador está haciendo. Es mucho mejor dar nombres de funciones significativos. Escribir algunos caracteres adicionales es barato, los problemas de mantenimiento a largo plazo son caros.
  • Pueden romper las expectativas implícitas de rendimiento . Por ejemplo, normalmente esperaría que fuera la búsqueda de un elemento en una matriz O(1). Pero con la sobrecarga del operador, someobject[i]fácilmente podría ser una O(n)operación dependiendo de la implementación del operador de indexación.

En realidad, hay muy pocos casos en los que la sobrecarga del operador tenga usos justificables en comparación con el uso de funciones regulares. Un ejemplo legítimo podría ser el diseño de una clase de números complejos para uso de los matemáticos, que comprenden las formas bien entendidas en que los operadores matemáticos se definen para los números complejos. Pero esto realmente no es un caso muy común.

Algunos casos interesantes a considerar:

  • Lisps : en general, no distingue en absoluto entre operadores y funciones, +es solo una función regular. Puede definir las funciones como desee (normalmente hay una forma de definirlas en espacios de nombres separados para evitar conflictos con el incorporado +), incluidos los operadores. Pero hay una tendencia cultural a usar nombres de funciones significativos, por lo que no se abusa mucho de esto. Además, en el prefijo de Lisp, la notación tiende a usarse exclusivamente, por lo que hay menos valor en el "azúcar sintáctico" que proporcionan las sobrecargas del operador.
  • Java : no permite la sobrecarga del operador. Esto es ocasionalmente molesto (para cosas como el caso de los números complejos), pero en promedio es probablemente la decisión de diseño correcta para Java, que pretende ser un lenguaje OOP simple y de propósito general. El código Java es en realidad bastante fácil de mantener para los desarrolladores con poca o mediana habilidad como resultado de esta simplicidad.
  • C ++ tiene una sobrecarga del operador muy sofisticada. A veces esto se abusa ( cout << "Hello World!"¿alguien?), Pero el enfoque tiene sentido dado el posicionamiento de C ++ como un lenguaje complejo que permite la programación de alto nivel y al mismo tiempo le permite acercarse mucho al metal para el rendimiento, por lo que puede, por ejemplo, escribir una clase de números complejos que se comporte exactamente como lo desee sin comprometer el rendimiento. Se entiende que es su responsabilidad si se dispara en el pie.
revs mikera
fuente
8

Dado que esta característica es bastante trivial de implementar, ¿por qué no es más común?

No es trivial de implementar (a menos que se implemente trivialmente). Tampoco te ayuda mucho, incluso si se implementa de manera ideal: las ganancias de legibilidad de la conmoción se compensan con las pérdidas de legibilidad por desconocimiento y opacidad. En resumen, es poco común porque no suele valer la pena el tiempo de los desarrolladores o los usuarios.

Dicho esto, puedo pensar en tres idiomas que lo hacen, y lo hacen de diferentes maneras:

  • Racket, un esquema, cuando no se trata de todo, S-expression-y permite y espera que escriba un analizador de lo que equivale a cualquier sintaxis que desee ampliar (y proporciona enlaces útiles para que esto sea manejable).
  • Haskell, un lenguaje de programación puramente funcional, permite definir cualquier operador que consista únicamente en la puntuación y le permite proporcionar un nivel de fijación (10 disponibles) y una asociatividad. Los operadores ternarios, etc. se pueden crear a partir de operadores binarios y funciones de orden superior.
  • Agda, un lenguaje de programación dependientemente tipado, es extremadamente flexible con los operadores (artículo aquí ) permitiendo que tanto if-then como if-then-else se definan como operadores en el mismo programa, pero su lexer, parser y evaluador están fuertemente acoplados como resultado.
Alex R
fuente
44
Usted ha dicho que "no es trivial" e inmediatamente enumeró tres idiomas con algunas implementaciones escandalosamente triviales. Entonces, es bastante trivial después de todo, ¿no?
SK-logic
7

Una de las principales razones por las que se desalienta a los operadores personalizados es porque cualquier operador puede querer / puede hacer cualquier cosa.

Por ejemplo cstream, la sobrecarga de desplazamiento a la izquierda muy criticada.

Cuando un lenguaje permite sobrecargas del operador, generalmente existe un estímulo para mantener el comportamiento del operador similar al comportamiento base para evitar confusiones.

Además, los operadores definidos por el usuario hacen que el análisis sea mucho más difícil, especialmente cuando también hay reglas de preferencia personalizadas.

freak trinquete
fuente
66
No veo cómo las partes sobre la sobrecarga del operador se aplican a la definición de operadores completamente nuevos.
He visto un uso muy bueno de la sobrecarga del operador (bibliotecas de álgebra lineal) y muy malo, como mencionaste. No creo que este sea un buen argumento contra los operadores personalizados. El análisis puede ser un problema, pero es bastante obvio cuando se espera un operador. Después del análisis, depende del generador de código buscar el significado del operador.
beatgammit
3
¿Y en qué se diferencia esto de la sobrecarga de métodos?
Jörg W Mittag
@ JörgWMittag con sobrecarga de método hay (la mayoría de las veces) un nombre significativo adjunto. con un símbolo que es más difícil de explicar de manera sucinta lo que sucederá
trinquete monstruo
1
@ratchetfreak: Bueno. +suma dos cosas, las -resta, las *multiplica. Mi sensación es que nadie obliga al programador a hacer que la función / método addrealmente agregue algo y doNothingpueda lanzar armas nucleares. Y a.plus(b.minus(c.times(d)).times(e)es mucho menos legible entonces a + (b - c * d) * e(bono adicional, donde en la primera picadura hay un error en la transcripción). No veo cómo primero es más significativo ...
Maciej Piechotka
4

No usamos operadores definidos por el usuario por la misma razón que no usamos palabras definidas por el usuario. Nadie llamaría a su función "sworp". La única forma de transmitir su pensamiento a otra persona es usar un lenguaje compartido. Y eso significa que tanto las palabras como los signos (operadores) deben ser conocidos por la sociedad para la que está escribiendo su código.

Por lo tanto, los operadores que ve en uso en los lenguajes de programación son los que nos han enseñado en la escuela (aritmética) o los que se han establecido en la comunidad de programación, como por ejemplo los operadores booleanos.

Vagif Verdi
fuente
1
Agregue a eso la capacidad de descubrir el significado detrás de una función. En el caso de "sworp" puede pegarlo fácilmente en un cuadro de búsqueda y encontrar su documentación. Pegar a un operador en un motor de búsqueda es un juego de pelota completamente diferente. Nuestros motores de búsqueda actuales simplemente apestan en encontrar operadores, y he perdido mucho tiempo tratando de encontrar los significados en algunos casos.
Mark H
1
¿Cuánto "escuela" cuentas? Por ejemplo, creo que ∈ elemes una gran idea y ciertamente un operador que todos deberían entender, pero otros parecen estar en desacuerdo.
Tikhon Jelvis
1
No es el problema con el símbolo. Es el problema con el teclado. Por ejemplo, uso emacs haskell-mode con el embellecedor Unicode habilitado. Y convierte automáticamente los operadores ASCII en símbolos Unicode. Pero no podría escribirlo si no hubiera un equivalente ASCII.
Vagif Verdi
2
Los lenguajes naturales se componen únicamente de las "palabras definidas por el usuario" y nada más. Y algunos idiomas realmente fomentan la formación de palabras personalizadas.
SK-logic
4

En cuanto a los lenguajes que admiten tal sobrecarga: Scala sí, de hecho de una manera mucho más limpia y mejor puede C ++. La mayoría de los caracteres se pueden usar en los nombres de funciones, por lo que puede definir operadores como! + * = ++, si lo desea. Hay soporte incorporado para infijo (para todas las funciones que toman un argumento). Creo que también puedes definir la asociatividad de tales funciones. Sin embargo, no puedes definir la precedencia (solo con trucos feos, mira aquí ).

revs kutschkem
fuente
4

Una cosa que aún no se ha mencionado es el caso de Smalltalk, donde todo (incluidos los operadores) es un mensaje enviado. A los "operadores" les gusta +, |etc., en realidad son métodos unarios.

Todos los métodos pueden anularse, por lo que a + bsignifica suma de enteros si ay bson ambos enteros, y significa suma de vectores si ambos son OrderedCollections.

No hay reglas de precedencia, ya que estas son solo llamadas a métodos. Esto tiene una implicación importante para la notación matemática estándar: 3 + 4 * 5medios (3 + 4) * 5, no 3 + (4 * 5).

(Este es un gran obstáculo para los novatos de Smalltalk. Romper las reglas de las matemáticas elimina un caso especial, de modo que toda la evaluación del código se lleva a cabo de manera uniforme de izquierda a derecha, haciendo el lenguaje mucho más simple).

Frank Shearar
fuente
3

Estás luchando contra dos cosas aquí:

  1. ¿Por qué los operadores existen en idiomas en primer lugar?
  2. ¿Cuál es la virtud de los operadores sobre las funciones / métodos?

En la mayoría de los idiomas, los operadores no se implementan realmente como funciones simples. Es posible que tengan algunos andamios de funciones, pero el compilador / tiempo de ejecución es explícitamente consciente de su significado semántico y de cómo traducirlos de manera eficiente al código de la máquina. Esto es mucho más cierto incluso en comparación con las funciones integradas (por lo que la mayoría de las implementaciones tampoco incluyen todos los gastos generales de llamadas a funciones en su implementación). La mayoría de los operadores son abstracciones de nivel superior en las instrucciones primitivas que se encuentran en las CPU (que es en parte por qué la mayoría de los operadores son aritméticos, booleanos o bit a bit). Puede modelarlas como funciones "especiales" (llámelas "primitivas" o "integradas" o "nativas" o lo que sea), pero para hacer eso genéricamente se requiere un conjunto de semánticas muy robusto para definir tales funciones especiales. La alternativa es tener operadores integrados que se parezcan semánticamente a los operadores definidos por el usuario, pero que de otro modo invoquen rutas especiales en el compilador. Eso va en contra de la respuesta a la segunda pregunta ...

Aparte del problema de traducción automática que mencioné anteriormente, a nivel sintáctico, los operadores no son realmente diferentes de las funciones. Son características distintivas que tienden a ser concisas y simbólicas, lo que sugiere una característica adicional significativa que deben tener para ser útiles: deben tener un significado / semántica ampliamente comprensible para los desarrolladores. Los símbolos cortos no transmiten mucho significado a menos que sea una mano corta para un conjunto de semánticas que ya se entienden. Eso hace que los operadores definidos por el usuario sean inherentemente inútiles, ya que, por su propia naturaleza, no son tan ampliamente entendidos. Tienen tanto sentido como los nombres de funciones de una o dos letras.

Las sobrecargas del operador de C ++ proporcionan un terreno fértil para examinar esto. La mayoría de los "abusos" por sobrecarga del operador se presentan en forma de sobrecargas que rompen parte del contrato semántico que se entiende ampliamente (un ejemplo clásico es una sobrecarga del operador + tal que a + b! = B + a, o donde + modifica cualquiera de sus operandos).

Si observa Smalltalk, que permite la sobrecarga de operadores y los operadores definidos por el usuario, puede ver cómo un lenguaje podría hacerlo y qué tan útil sería. En Smalltalk, los operadores son simplemente métodos con diferentes propiedades sintácticas (es decir, están codificados como infinarios binarios). El lenguaje utiliza "métodos primitivos" para operadores y métodos acelerados especiales. Usted encuentra que pocos si se crean operadores definidos por el usuario, y cuando lo son, tienden a no usarse tanto como el autor probablemente pretendía que se usaran. Incluso el equivalente de una sobrecarga del operador es raro, porque es principalmente una pérdida neta definir una nueva función como operador en lugar de un método, ya que este último permite una expresión de la semántica de la función.

Christopher Smith
fuente
1

Siempre he encontrado que las sobrecargas de operadores en C ++ son un atajo conveniente para un equipo de un solo desarrollador, pero que causa todo tipo de confusión a largo plazo simplemente porque las llamadas al método están "ocultas" de una manera que no es fácil para que las herramientas como doxygen se separen, y las personas necesitan comprender los modismos para usarlos adecuadamente.

A veces es mucho más difícil de entender de lo que cabría esperar, incluso. Érase una vez, en un gran proyecto C ++ multiplataforma, decidí que sería una buena idea normalizar la forma en que se construían los caminos creando un FilePathobjeto (similar al Fileobjeto de Java ), que tendría operador / utilizado para concatenar otro parte de la ruta en él (para que pueda hacer algo como File::getHomeDir()/"foo"/"bar"y haría lo correcto en todas nuestras plataformas compatibles). Todos los que lo vieron esencialmente dirían: "¿Qué demonios? ¿División de cuerdas? ... Oh, eso es lindo, pero no confío en que haga lo correcto".

Del mismo modo, hay muchos casos en la programación de gráficos u otras áreas donde las matemáticas vectoriales / matriciales suceden muchas veces donde es tentador hacer cosas como Matrix * Matrix, Vector * Vector (punto), Vector% Vector (cross), Matrix * Vector ( transformación de matriz), matriz ^ Vector (transformación de matriz de caso especial que ignora la coordenada homogénea, útil para las normales de superficie), y así sucesivamente, pero si bien ahorra un poco de tiempo de análisis para la persona que escribió la biblioteca matemática de vectores, solo termina hasta confundir el tema más para otros. Simplemente no vale la pena.

mullido
fuente
0

Las sobrecargas de operadores son una mala idea por la misma razón que las sobrecargas de métodos son una mala idea: el mismo símbolo en la pantalla tendría diferentes significados dependiendo de lo que esté a su alrededor. Esto lo hace más difícil para la lectura casual.

Dado que la legibilidad es un aspecto crítico de la mantenibilidad, siempre debe evitar la sobrecarga (excepto en algunos casos muy especiales). Es mucho mejor que cada símbolo (ya sea operador o identificador alfanumérico) tenga un significado único que sea independiente.

Para ilustrar: cuando lee un código desconocido, si encuentra un nuevo identificador alfanumérico que no conoce, al menos tiene la ventaja de que sabe que no lo sabe. Entonces puedes ir a buscarlo. Sin embargo, si ve un identificador u operador común del que conoce el significado, es mucho menos probable que note que en realidad se ha sobrecargado para tener un significado completamente diferente. Para saber qué operadores se han sobrecargado (en una base de código que hizo un uso generalizado de la sobrecarga), necesitaría un conocimiento práctico del código completo, incluso si solo desea leer una pequeña parte de él. Esto dificultaría que los nuevos desarrolladores se pusieran al día con ese código, y sería imposible atraer a las personas para un trabajo pequeño. Esto puede ser bueno para la seguridad laboral del programador, pero si usted es responsable del éxito de la base del código, debe evitar esta práctica a toda costa.

Debido a que los operadores son de tamaño pequeño, los operadores de sobrecarga permitirían un código más denso, pero hacer que el código sea denso no es un beneficio real. Una línea con el doble de lógica tarda el doble en leerse. Al compilador no le importa. El único problema es la legibilidad humana. Dado que hacer que el código sea compacto no mejora la legibilidad, la compacidad no tiene ningún beneficio real. Siga adelante y tome el espacio, y dé a las operaciones únicas un identificador único, y su código tendrá más éxito a largo plazo.

AgilePro
fuente
"el mismo símbolo en la pantalla tendría diferentes significados dependiendo de lo que esté a su alrededor", ya es el caso para muchos operadores en la mayoría de los idiomas.
rkj
-1

Dificultades técnicas para manejar la precedencia y el análisis complejo, dejando de lado, creo que hay algunos aspectos de lo que es un lenguaje de programación que debe considerarse.

Los operadores son generalmente construcciones lógicas cortas que están bien definidas y documentadas en el lenguaje central (comparar, asignar ...). También suelen ser difíciles de entender sin documentación (comparar a^bcon, xor(a,b)por ejemplo). Hay un número bastante limitado de operadores que en realidad podrían tener sentido en la programación normal (>, <, =, + etc.).

Mi idea es que es mejor atenerse a un conjunto de operadores bien definidos en un idioma, y ​​luego permitir la sobrecarga de esos operadores (dada una recomendación suave de que los operadores deberían hacer lo mismo, pero con un tipo de datos personalizado).

Sus casos de uso de ~y |realmente serían posibles con una simple sobrecarga del operador (C #, C ++, etc.). DSL es un área de uso válida, pero probablemente una de las únicas áreas válidas (desde mi punto de vista). Sin embargo, creo que hay mejores herramientas para crear nuevos idiomas dentro. Ejecutar un verdadero lenguaje DSL dentro de otro idioma no es tan difícil usando cualquiera de esas herramientas de compilación-compilación. Lo mismo ocurre con el "argumento LUA extendido". Lo más probable es que un idioma se defina principalmente para resolver problemas de una manera específica, no para ser una base para sub-idiomas (existen excepciones).

Petter Nordlander
fuente
-1

Otro factor es que no siempre es sencillo definir una operación con los operadores disponibles. Quiero decir, sí, para cualquier tipo de número, el operador '*' puede tener sentido, y generalmente se implementan en el lenguaje o en los módulos existentes. Pero en el caso de las clases complejas típicas que necesita definir (cosas como ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter, etc.) ese comportamiento no está claro ... ¿Qué significa sumar o restar un número a una Dirección? Multiplicar dos direcciones?

Claro, puede definir que agregar una cadena a una clase ShippingAddress significa una operación personalizada como "reemplazar la línea 1 en la dirección" (en lugar de la función "setLine1") y agregar un número es "reemplazar el código postal" (en lugar de "setZipCode") , pero el código no es muy legible y confuso. Por lo general, pensamos que los operadores se utilizan en tipos / clases básicos, ya que su comportamiento es intuitivo, claro y consistente (al menos una vez que esté familiarizado con el lenguaje). Piense en tipos como Integer, String, ComplexNumbers, etc.

Por lo tanto, incluso si la definición de operadores puede ser muy útil en algunos casos específicos, su implementación en el mundo real es bastante limitada, ya que el 99% de los casos en que eso será una clara victoria ya están implementados en el paquete de idioma básico.

Khelben
fuente