Почему знак минус «-», как правило, не перегружается так же, как знак плюс?

64

Знак плюс +используется для сложения и конкатенации строк, но его компаньон: знак минус -, как правило, не виден для обрезки строк или какого-либо другого случая, кроме вычитания. В чем может быть причина или ограничения для этого?

Рассмотрим следующий пример в JavaScript:

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

a-b == NaN
// but
a+b == "abcdefgefg"
Дигвиджай Ядав
источник
35
какой "уу" надо убрать?
Гашач
12
Если я пойду с поведением знака «+», то правее всего имеет смысл сделать это.
Дигвиджай Ядав
46
Достаточно плохо, что бинарный +оператор перегружен двумя совершенно не связанными значениями «числовое сложение» и «конкатенация строк». К счастью, некоторые языки предоставляют отдельный оператор конкатенации, такой как .(Perl5, PHP), ~(Perl6), &(VB), ++(Haskell),…
am
6
@MasonWheeler Они используют ->(подумайте о разыменовании доступа к элементу в C, поскольку вызовы виртуальных методов обязательно включают в себя указатель-подобную косвенность). Не существует закона языковой структуры, который требует вызовов методов / доступа к элементу для использования .оператора, хотя это все более распространенное соглашение. Знаете ли вы, что в Smalltalk нет оператора вызова метода? Простое сопоставление object methodдостаточно.
Амон
20
Python делает перегрузку минус, для вычитания набора (и это может быть перегружено также в пользовательских типах). Наборы Python также перегружают большинство побитовых операторов для пересечения / объединения / и т.д.
Кевин

Ответы:

116

Короче говоря, нет никаких особенно полезных операций, подобных вычитанию, над строками, с которыми люди хотели бы написать алгоритмы.

+Оператора как правило , обозначает операцию аддитивного моноида , то есть ассоциативная операция с единицей:

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

Имеет смысл использовать этот оператор для таких вещей, как сложение целых чисел, конкатенация строк и объединение множеств, поскольку все они имеют одинаковую алгебраическую структуру:

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

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

И мы можем использовать его для написания удобных алгоритмов, таких как concatфункция, которая работает с последовательностью любых «конкатенируемых» вещей, например:

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

Когда происходит вычитание -, вы обычно говорите о структуре группы , которая добавляет обратный -A для каждого элемента A, так что:

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

И хотя это имеет смысл для таких вещей, как целочисленное и вычитание с плавающей точкой, или даже для разности множеств, это не имеет особого смысла для строк и списков. Что является обратным "foo"?

Существует структура, называемая отменяющим моноидом , которая не имеет инверсий, но имеет свойство отмены , так что:

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

Это структура, которую вы описываете, где "ab" - "b" == "a", но "ab" - "c"не определены. Просто у нас не так много полезных алгоритмов, которые используют эту структуру. Я предполагаю, что если вы думаете о конкатенации как о сериализации, то вычитание может быть использовано для какого-то анализа.

Джон Перди
источник
2
Для множеств (и множественных множеств) вычитание имеет смысл, потому что в отличие от последовательностей порядок элемента не имеет значения.
CodesInChaos
@CodesInChaos: я добавил упоминание о них, но мне было не очень удобно ставить наборы в качестве примера группы - я не верю, что они образуют один, так как вы обычно не можете построить обратный набор.
Джон Перди
12
На самом деле, +операция также является коммутативной для чисел, то есть A+B == B+Aделает ее плохим кандидатом для конкатенации строк. Это, плюс запутанный приоритет оператора, делает использование +для конкатенации строк исторической ошибкой. Тем не менее, это правда , что использование -для любой операции струны сделаны вещи гораздо хуже ...
Хольгер
2
@ Дархогг: Точно! PHP позаимствован .у Perl; это ~в Perl6, возможно, другие.
Джон Пурди
1
@MartinBeckett, но вы можете видеть, что поведение может сбивать с толку .text.gz.text...
Борис Паук
38

Потому что конкатенация любых двух допустимых строк всегда является допустимой операцией, но обратное неверно.

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

Что a - bздесь должно быть? Там действительно нет хорошего способа ответить на этот вопрос, потому что сам вопрос не является действительным.

Мейсон Уилер
источник
31
@DigvijayYadav, если вы удалите 5 манго из 5 яблок, должен ли быть счетчик -5 манго? Это ничего не делает? Можете ли вы определить это достаточно хорошо, чтобы его можно было широко принять и включить во все компиляторы и интерпретаторы языков, чтобы использовать этот оператор в этой форме? Это большая проблема здесь.
JB Кинг
28
@DigvijayYadav: Итак, вы только что описали два возможных способа реализации этого, и есть хороший аргумент, чтобы считать каждый из них допустимым, поэтому мы уже напутали идею задания этой операции. : P
Мейсон Уилер
13
Мне кажется, что @smci 5 + Falseдолжно быть ошибкой , поскольку число не является логическим, а логическое не является числом.
Мейсон Уилер
6
@JanDvorak: В этом нет ничего особенно «Хаскелли»; это основная строгая типизация.
Мейсон Уилер
5
@DigvijayYadav Так (a+b)-b = a(надеюсь!), Но (a-b)+bиногда a, иногда a+bзависит от того, bявляется ли подстрока aили нет? Что это за безумие?
28

Потому что -оператору для манипулирования строками не хватает «семантической сплоченности». Операторы должны быть перегружены, только когда абсолютно ясно, что перегрузка делает со своими операндами, а вычитание строки не соответствует этой полосе.

Следовательно, вызовы методов предпочтительнее:

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

В языке C # мы используем +для конкатенации строк, потому что форма

var result = string1 + string2 + string3;

вместо

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

это удобно и, возможно, легче читать, даже если вызов функции, вероятно, более «правильный» с семантической точки зрения.

+Оператор может действительно означать только одну вещь в этом контексте. Это не верно как для -, так как понятия вычитая строки неоднозначен (вызов функции Replace(source, oldValue, newValue)с ""как newValueпараметр снимает все сомнения, и эта функция может быть использована для изменения подстроки, а не просто удалить их).

Проблема, конечно, состоит в том, что перегрузка оператора зависит от типов, передаваемых оператору, и если вы передадите строку, где должно быть число, вы можете получить результат, который вы не ожидали. Кроме того, для многих конкатенаций (т. Е. В цикле) StringBuilderпредпочтительным является объект, поскольку при каждом использовании +создается новая строка, и производительность может пострадать. Таким образом, +оператор даже не подходит во всех контекстах.

Существуют перегрузки операторов, которые имеют лучшую семантическую связность, чем +оператор для конкатенации строк. Вот тот, который добавляет два комплексных числа:

public static Complex operator +(Complex c1, Complex c2) 
{
    return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}
Роберт Харви
источник
8
+1 Учитывая две строки, A и B, я могу думать о AB как о «удалить конечный B из конца A», «удалить экземпляр B где-нибудь в A», «удалить все экземпляры B где-нибудь в A , "или даже" удалить все символы, найденные в B, из A. "
Cort Ammon
8

Groovy язык действительно позволяет -:

println('ABC'-'B')

возвращает:

AC

А также:

println( 'Hello' - 'World' )

возвращает:

Hello

А также:

println('ABABABABAB' - 'B')

возвращает:

AABABABAB
Вим Деблавве
источник
11
Интересно - так он решит удалить первое вхождение? Хороший пример совершенно нелогичного поведения.
Халк
9
Следовательно, мы имеем то, что ('ABABABABA' + 'B') - 'B'далеко не совпадает с начальным значением 'ABABABABA'.
CVn
3
@ MichaelKjörling OTOH, (A + B) - A == Bдля каждого A и B. Могу ли я назвать это левым вычитанием?
Джон Дворак
2
Haskell имеет ++для конкатенации. Он работает с любым списком, а строка - это просто список символов. Это также имеет \\, что удаляет первый вхождение каждого элемента в правом аргументе из левого аргумента.
Джон Дворак
3
Я чувствую, что эти примеры именно поэтому не должно быть оператора минуса для строк. Это непоследовательное и не интуитивное поведение. Когда я думаю о "-" я точно не думаю ", удалите первый экземпляр соответствующей строки, если это произойдет, иначе просто ничего не делайте".
Эндерланд
6

Знак плюса, вероятно, контекстуально имеет смысл в большем количестве случаев, но контрпример (возможно, исключение, которое подтверждает правило) в Python - это объект set, который предусматривает, -но не обеспечивает +:

>>> 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'

Не имеет смысла использовать +знак, потому что намерение может быть неоднозначным - означает ли это установить пересечение или объединение? Вместо этого он использует |для объединения и &для пересечения:

>>> set('abc') | set('bcd')
set(['a', 'c', 'b', 'd'])
>>> set('abc') & set('bcd')
set(['c', 'b'])
Аарон Холл
источник
2
Это более вероятно, потому что вычитание набора определено в математике, а добавление набора - нет.
Мердад
Использование "-" кажется хитрым; что действительно необходимо, так это оператор «но не», который также был бы полезен при выполнении побитовой арифметики с целыми числами. Если бы 30 ~ & 7 было 24, то использование ~ & с наборами хорошо подошло бы с & и | хотя в наборах отсутствует оператор ~.
суперкат
1
set('abc') ^ set('bcd')возвращается set(['a', 'd']), если вы спрашиваете о симметричной разности.
Аарон Холл
3

« -» используется в некоторых составных словах (например, «на месте») для объединения разных частей в одно и то же слово. Почему мы не используем " -" для объединения различных строк в языках программирования? Я думаю, что это будет иметь смысл! К черту эту +ерунду!

Однако, давайте попробуем взглянуть на это немного более абстрактно.

Как бы вы определили строковую алгебру? Какие операции вы бы провели, и какие законы для них? Какими будут их отношения?

Помните, что не может быть никакой двусмысленности! Каждый возможный случай должен быть четко определен, даже если это означает, что это невозможно сделать! Чем меньше ваша алгебра, тем легче это сделать.

Например, что на самом деле означает сложение или вычитание двух строк?

Если вы добавите две строки (например, let a = "aa"и b = "bb"), вы получите aabbв результате a + b?

Как насчет b + a? Это будет bbaa? Почему нет aabb? Что произойдет, если вы вычтете aaрезультат своего сложения? Будет ли в вашей строке понятие отрицательного количества aa?

Теперь вернитесь к началу этого ответа и подставьте spaceshuttleвместо строки. Чтобы обобщить, почему любая операция определена или не определена для любого типа?

Я пытаюсь подчеркнуть, что ничто не мешает вам создать алгебру для чего-либо. Может быть трудно найти значимые операции или даже полезные операции для этого.

Для строк конкатенация является практически единственным разумным вариантом, с которым я когда-либо сталкивался. Не имеет значения, какой символ используется для представления операции.

Zavior
источник
1
«Для струнных конкатенация является практически единственной разумной, с которой я когда-либо сталкивался» . Тогда вы не согласны с Python 'xy' * 3 == 'xyxyxy'?
smci
3
@smci, это просто умножение-как-повторение-сложение , конечно?
Джоншарп
что такое правильный оператор для объединения космических шаттлов?
Мистер Миндор
4
@ Mr.Mindor Backspace ... чтобы убрать пробел между шаттлами.
YoungJohn