Чому знак мінус '-', як правило, не перевантажується так само, як знак плюс?


64

Знак "плюс" +використовується для додавання і для з'єднання рядків, але його супутник: знак мінус -, як правило, не сприймається для обрізки рядків або іншого випадку, окрім віднімання. Що може бути причиною чи обмеженнями для цього?

Розглянемо наступний приклад у JavaScript:

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

a-b == NaN
// but
a+b == "abcdefgefg"

35
який "yy" слід видалити?
gashach

12
Якщо я йду з поведінкою знака "+", то право має найбільше сенс.
Digvijay Yadav

46
Досить погано, що двійковий +оператор перевантажений двома абсолютно неспорідненими значеннями "числове додавання" та "з'єднання рядків". На щастя, деякі мови надають окремий оператор конкатенації, такий як .(Perl5, PHP), ~(Perl6), &(VB), ++(Haskell),…
amon

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"?

Існує структура, яка називається скасованим моноїдом , яка не має зворотів, але має властивість скасування , так що:

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

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


2
Для множин (і множинних множин) віднімання має сенс, оскільки на відміну від послідовностей, порядок елемента не має значення.
CodesInChaos

@CodesInChaos: Я додав згадки про них, але мені не дуже зручно було ставити набори як приклад групи - я не вірю, що вони складають один, оскільки ти не можеш побудувати зворотну множину.
Джон Перді

12
Власне, +операція також є комутативною для чисел, тобто A+B == B+A, це робить її поганим кандидатом для конкатенації рядків. Це, плюс заплутане пріоритет оператора, використовує +для об'єднання рядків історичну помилку. Однак це правда, що використання -будь-якої струнної операції значно погіршило ситуацію…
Холгер

2
@Darkhogg: Правильно! PHP, запозичений .у Perl; це ~в Perl6, можливо, в інших.
Джон Перді

1
@MartinBeckett, але ви бачите, що поведінка може бентежити з .text.gz.text...
Борис Павук

38

Тому що об'єднання будь-яких двох дійсних рядків - це завжди дійсна операція, але навпаки не відповідає дійсності.

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

Що a - bтут повинно бути? Дійсно відповісти на це питання немає, тому що саме запитання не є дійсним.


31
@DigvijayYadav, якщо ви видалите 5 манго з 5 яблук, чи має бути тоді лічильник -5 манго? Це нічого не робить? Чи можете ви визначити це досить добре, щоб його можна було широко прийняти та розмістити у всіх компіляторах та інтерпретаторах мов для використання цього оператора у цій формі? Це найбільший виклик тут.
JB King

28
@DigvijayYadav: Отже, ви щойно описали два можливі способи цього здійснити, і є хороший аргумент, щоб вважати кожного з них дійсним, тому ми вже робимо безлад ідеї уточнення цієї операції. : P
Мейсон Уілер

13
@smci Здається, я, 5 + Falseочевидно, має бути помилкою , оскільки число не булеве, а булеве - не число.
Мейсон Уілер

6
@JanDvorak: У цьому немає нічого особливого "Haskelly"; це базовий сильний набір тексту.
Мейсон Уілер

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, з А. "
Корт Аммон

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('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, то використання ~ & з наборами добре би відповідало & & | навіть якщо набору не вистачає оператора ~.
supercat

1
set('abc') ^ set('bcd')повертається set(['a', 'd']), якщо ви запитуєте про симетричну різницю.
Аарон Хол

3

" -" використовується в деяких складних словах (наприклад, "на місці") для приєднання різних частин до одного слова. Чому ми не використовуємо " -" для об'єднання різних рядків у мовах програмування? Я думаю, це мало б ідеальний сенс! До біса з цією +нісенітницею!

Однак спробуємо розглянути це з трохи більш абстрактного кута.

Як би ви визначили рядову алгебру? Які операції ви мали б, і які закони дотримувалися б їх? Якими були б їхні стосунки?

Пам’ятайте, двозначності може бути абсолютно немає! Кожен можливий випадок повинен бути чітко визначений, навіть якщо це означає, що це неможливо зробити! Чим менше ваша алгебра, тим простіше це зробити.

Наприклад, що насправді означає додавання або віднімання двох рядків?

Якщо ви додасте два рядки (наприклад, дозвольте a = "aa"і b = "bb"), ви отримаєте aabbрезультат a + b?

Як щодо b + a? Це було б bbaa? Чому ні aabb? Що станеться, якщо ви віднімаєте aaрезультат додавання? Чи має у вашому рядку поняття від’ємної кількості aa?

Тепер поверніться до початку цієї відповіді та замініть spaceshuttleрядок замість рядка. Для узагальнення, чому для будь-якого типу визначена чи не визначена будь-яка операція?

Я намагаюся зробити те, що ніщо не заважає вам створити алгебру ні для чого. Можливо, буде важко знайти значущі операції або навіть корисні для них операції.

Що стосується струн, то поєднання - це майже єдиний розумний, з яким я коли-небудь стикався. Не має значення, який символ використовується для представлення операції.


1
"Що стосується струн, об'єднання - це майже єдиний розумний, який я коли-небудь стикався" . Тоді ви не згодні з Python's 'xy' * 3 == 'xyxyxy'?
smci

3
@smci це просто множення як повторне додавання , безумовно?
jonrsharpe

який правильний оператор для об'єднання пробілів?
Містер Міндор

4
@ BackSpace Mr.Mindor ... щоб видалити простір між пробілами.
YoungJohn
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.