¿Por qué el signo menos, '-', generalmente no está sobrecargado de la misma manera que el signo más?

64

El signo más +se usa para la suma y para la concatenación de cadenas, pero su compañero: el signo menos -, generalmente no se ve para el recorte de cadenas o algún otro caso que no sea la resta. ¿Cuál podría ser la razón o las limitaciones para eso?

Considere el siguiente ejemplo en JavaScript:

var a = "abcdefg";
var b = "efg";

a-b == NaN
// but
a+b == "abcdefgefg"
Digvijay Yadav
fuente
35
¿Qué "yy" debe eliminarse?
gashach
12
Si sigo el comportamiento del signo '+', entonces el más adecuado tiene sentido.
Digvijay Yadav
46
Ya es bastante malo que el +operador binario esté sobrecargado con los dos significados totalmente independientes "suma numérica" ​​y "concatenación de cadenas". Afortunadamente, algunos idiomas proporcionan un operador de concatenación separado como .(Perl5, PHP), ~(Perl6), &(VB), ++(Haskell), ...
amon
66
@MasonWheeler Utilizan ->(piense en desreferenciar el acceso de miembros en C, ya que las llamadas a métodos virtuales necesariamente implican una indirección similar a un puntero). No existe una ley de diseño de lenguaje que requiera llamadas a métodos / acceso de miembros para usar un .operador, aunque es una convención cada vez más común. ¿Sabía que Smalltalk no tiene operador de llamada a método? La yuxtaposición simple object methodes suficiente.
amon
20
Python hace sobrecarga menos, para conjunto de sustracción (y puede ser sobrecargado en tipos definidos por el usuario también). Los conjuntos de Python también sobrecargan la mayoría de los operadores bit a bit para intersección / unión / etc.
Kevin

Respuestas:

116

En resumen, no hay operaciones de sustracción particularmente útiles en cadenas con las que la gente haya querido escribir algoritmos.

El +operador generalmente denota la operación de un monoide aditivo , es decir, una operación asociativa con un elemento de identidad:

  • A + (B + C) = (A + B) + C
  • A + 0 = 0 + A = A

Tiene sentido usar este operador para cosas como la suma de enteros, la concatenación de cadenas y la unión de conjuntos porque todos tienen la misma estructura algebraica:

1 + (2 + 3) == (1 + 2) + 3
1 + 0 == 0 + 1 == 1

"a" + ("b" + "c") == ("a" + "b") + "c"
"a" + "" == "" + "a" == "a"

Y podemos usarlo para escribir algoritmos útiles como una concatfunción que funciona en una secuencia de cualquier cosa "concatenable", por ejemplo:

def concat(sequence):
    return sequence.reduce(+, 0)

Cuando -se involucra la resta , generalmente se habla de la estructura de un grupo , que agrega una inversa -A para cada elemento A, de modo que:

  • A + −A = −A + A = 0

Y si bien esto tiene sentido para cosas como la resta de enteros y coma flotante, o incluso para establecer diferencias, no tiene mucho sentido para cadenas y listas. ¿Cuál es el inverso de "foo"?

Hay una estructura llamada monoide canceroso , que no tiene inversas, pero tiene la propiedad de cancelación , de modo que:

  • A - A = 0
  • A - 0 = A
  • (A + B) - B = A

Esta es la estructura que describe, donde "ab" - "b" == "a", pero "ab" - "c"no está definida. Es solo que no tenemos muchos algoritmos útiles que usen esta estructura. Supongo que si piensas en la concatenación como serialización, la resta podría usarse para algún tipo de análisis.

Jon Purdy
fuente
2
Para los conjuntos (y conjuntos múltiples), la sustracción tiene sentido, porque a diferencia de las secuencias, el orden del elemento no importa.
CodesInChaos
@CodesInChaos: agregué una mención de ellos, pero no me sentí realmente cómodo poniendo conjuntos como ejemplo de un grupo; no creo que formen uno, ya que generalmente no se puede construir el inverso de un conjunto.
Jon Purdy
12
En realidad, la +operación también es conmutativa para los números, es decir A+B == B+A, lo que lo convierte en un mal candidato para la concatenación de cadenas. Esto, más la confusa prioridad del operador, hace que el uso +de la concatenación de cadenas sea un error histórico. Sin embargo, es cierto que usar -cualquier operación de cadena empeoró las cosas ...
Holger
2
@Darkhogg: ¡Correcto! PHP prestado .de Perl; es ~en Perl6, posiblemente otros.
Jon Purdy
1
@MartinBeckett, pero puedes ver que el comportamiento puede ser confuso con .text.gz.text...
Boris the Spider
38

Porque la concatenación de dos cadenas válidas siempre es una operación válida, pero lo contrario no es cierto.

var a = "Hello";
var b = "World";

¿Qué debería a - bestar aquí? Realmente no hay una buena manera de responder esa pregunta, porque la pregunta en sí no es válida.

Mason Wheeler
fuente
31
@DigvijayYadav, si quitas 5 mangos de 5 manzanas, ¿tiene que haber un contador de 5 mangos? ¿No hace nada? ¿Puede definir esto lo suficientemente bien como para que sea ampliamente aceptado y puesto en todos los compiladores e intérpretes de idiomas para usar este operador en este formulario? Ese es el gran desafío aquí.
JB King
28
@DigvijayYadav: Así que acabas de describir dos formas posibles de implementar esto, y hay un buen argumento para considerar cada uno como válido, por lo que ya estamos haciendo un lío con la idea de especificar esta operación. : P
Mason Wheeler
13
@smci Me parece que 5 + Falseobviamente debería ser un error , ya que un número no es un booleano y un booleano no es un número.
Mason Wheeler
66
@ JanDvorak: No hay nada particularmente "Haskelly" sobre eso; Eso es escribir fuerte y básico.
Mason Wheeler
55
@DigvijayYadav Entonces (a+b)-b = a(¡con suerte!), Pero a (a-b)+bveces a, a veces, ¿ a+bdepende de si bes una subcadena ao no? ¿Qué locura es esta?
28

Porque el -operador para la manipulación de cadenas no tiene suficiente "cohesión semántica". Los operadores solo deben sobrecargarse cuando está absolutamente claro qué hace la sobrecarga con sus operandos, y la resta de cadenas no cumple con esa barra.

En consecuencia, se prefieren las llamadas a métodos:

public string Remove(string source, string toRemove)
public string Replace(string source, string oldValue, string newValue)

En el lenguaje C #, usamos +para la concatenación de cadenas porque el formulario

var result = string1 + string2 + string3;

en lugar de

var result = string.Concat(string1, string2, string3);

es conveniente y posiblemente más fácil de leer, a pesar de que una llamada a la función es probablemente más "correcta", desde un punto de vista semántico.

El +operador realmente solo puede significar una cosa en este contexto. Esto no es tan cierto -, ya que la noción de restar cadenas es ambigua (la llamada Replace(source, oldValue, newValue)a la función con ""el newValueparámetro elimina toda duda, y la función se puede usar para alterar subcadenas, no solo eliminarlas).

El problema, por supuesto, es que la sobrecarga del operador depende de los tipos que se pasan al operador, y si pasa una cadena donde debería haber estado un número, puede obtener un resultado que no esperaba. Además, para muchas concatenaciones (es decir, en un bucle), StringBuilderes preferible un objeto, ya que cada uso de +crea una nueva cadena y el rendimiento puede verse afectado. Por lo tanto, el +operador ni siquiera es apropiado en todos los contextos.

Hay sobrecargas del operador que tienen una mejor cohesión semántica que el +operador para la concatenación de cadenas. Aquí hay uno que agrega dos números complejos:

public static Complex operator +(Complex c1, Complex c2) 
{
    return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}
Robert Harvey
fuente
8
+1 Dadas dos cadenas, A y B, puedo pensar en AB como "eliminar una B al final del final de A", "eliminar una instancia de B de algún lugar en A", "eliminar todas las instancias de B de algún lugar de A" , "o incluso" eliminar todos los caracteres encontrados en B de A ".
Cort Ammon
8

El lenguaje Groovy sí permite -:

println('ABC'-'B')

devoluciones:

AC

Y:

println( 'Hello' - 'World' )

devoluciones:

Hello

Y:

println('ABABABABAB' - 'B')

devoluciones:

AABABABAB
Wim Deblauwe
fuente
11
Interesante, ¿entonces elige eliminar la primera aparición? Un buen ejemplo para un comportamiento completamente contra-intuitivo.
Hulk
99
Por lo tanto, tenemos que ('ABABABABA' + 'B') - 'B'no es lo mismo que el valor inicial 'ABABABABA'.
un CVn
3
@ MichaelKjörling OTOH, (A + B) - A == Bpor cada A y B. ¿Puedo llamar a eso una resta izquierda?
John Dvorak
2
Haskell tiene ++para la concatenación. Funciona en cualquier lista y una cadena es solo una lista de caracteres. También tiene \\, lo que elimina la primera aparición de cada elemento en el argumento derecho del argumento izquierdo.
John Dvorak
3
Siento que estos ejemplos son exactamente por qué no debería haber un operador negativo para las cadenas. Es un comportamiento inconsistente y no intuitivo. Cuando pienso en "-" Seguro que no pienso ", elimine la primera instancia de la cadena correspondiente, si ocurre, de lo contrario simplemente no haga nada".
enderland
6

El signo más probablemente tenga sentido contextual en más casos, pero un contraejemplo (quizás una excepción que pruebe la regla) en Python es el objeto establecido, que proporciona -pero no +:

>>> set('abc') - set('bcd')
set(['a'])
>>> set('abc') + set('bcd')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'set' and 'set'

No tiene sentido usar el +signo porque la intención podría ser ambigua: ¿significa establecer intersección o unión? En cambio, usa |para unión y &para intersección:

>>> set('abc') | set('bcd')
set(['a', 'c', 'b', 'd'])
>>> set('abc') & set('bcd')
set(['c', 'b'])
Aaron Hall
fuente
2
Esto es más probable porque la resta de conjunto se define en matemáticas, pero la suma de conjunto no.
Mehrdad
El uso de "-" parece dudoso; lo que realmente se necesita es un operador "pero no" que también sería útil al realizar operaciones aritméticas bit a bit con enteros. Si 30 ~ y 7 fueran 24, entonces usar ~ & with sets encajaría bien con & y | aunque los conjuntos carecen de un operador ~.
supercat
1
set('abc') ^ set('bcd')devuelve set(['a', 'd']), si está preguntando sobre la diferencia simétrica.
Aaron Hall
3

" -" se usa en algunas palabras compuestas (por ejemplo, "en el sitio") para unir las diferentes partes en la misma palabra. ¿Por qué no usamos " -" para unir diferentes cadenas en lenguajes de programación? ¡Creo que tendría mucho sentido! ¡Al diablo con estas +tonterías!

Sin embargo, intentemos ver esto desde un ángulo un poco más abstracto.

¿Cómo definirías el álgebra de cuerdas? ¿Qué operaciones tendrías y qué leyes tendrían para ellos? ¿Cuáles serían sus relaciones?

¡Recuerde, puede que no haya absolutamente ninguna ambigüedad! ¡Todos los casos posibles deben estar bien definidos, incluso si eso significa decir que no es posible hacer esto! Cuanto más pequeño es el álgebra, más fácil se hace.

Por ejemplo, ¿qué significa realmente sumar o restar dos cadenas?

Si agrega dos cadenas (por ejemplo, let a = "aa"y b = "bb"), ¿obtendría aabbel resultado de a + b?

¿Qué tal b + a? ¿Sería eso bbaa? ¿Por qué no aabb? ¿Qué sucede si restas aadel resultado de tu suma? ¿Su cadena tendría un concepto de cantidad negativa aa?

Ahora regrese al comienzo de esta respuesta y sustituya en spaceshuttlelugar de la cadena. Para generalizar, ¿por qué hay alguna operación definida o no definida para ningún tipo?

El punto que estoy tratando de aclarar es que no hay nada que te impida crear un álgebra para nada. Puede ser difícil encontrar operaciones significativas, o incluso operaciones útiles para ello.

Para las cadenas, la concatenación es prácticamente la única sensata que he encontrado. No importa qué símbolo se use para representar la operación.

Comportamiento
fuente
1
"Para las cadenas, la concatenación es prácticamente la única sensata que he encontrado" . ¿Entonces no estás de acuerdo con Python 'xy' * 3 == 'xyxyxy'?
smci
3
@smci eso es solo multiplicación-como-repetición-suma , ¿seguro?
jonrsharpe
¿Cuál es el operador adecuado para concatenar las batallas espaciales?
Mr.Mindor
44
@ Mr.Mindor retroceso ... para eliminar el espacio entre los espacios vacíos.
YoungJohn