Tại sao dấu trừ, '-', thường không bị quá tải theo cùng một cách với dấu cộng?


64

Dấu cộng +được sử dụng để cộng và nối chuỗi, nhưng đồng hành của nó: dấu trừ -, thường không được nhìn thấy để cắt xâu chuỗi hoặc một số trường hợp khác ngoài phép trừ. Điều gì có thể là lý do hoặc hạn chế cho điều đó?

Hãy xem xét ví dụ sau trong JavaScript:

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

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

35
"yy" nào nên được loại bỏ?
gashach

12
Nếu tôi đi với hành vi của dấu '+', thì bên phải có ý nghĩa nhất.
Digvijay Yadav

46
Điều đó đủ tệ đến mức +toán tử nhị phân bị quá tải với hai ý nghĩa hoàn toàn không liên quan đến nhau. Rất may, một số ngôn ngữ cung cấp một toán tử ghép nối riêng như .(Perl5, PHP), ~(Perl6), &(VB), ++(Haskell),
Lỗi

6
@MasonWheeler Họ sử dụng ->(nghĩ rằng truy cập thành viên hội nghị trong C, vì các cuộc gọi phương thức ảo nhất thiết phải liên quan đến sự gián tiếp giống như con trỏ). Không có luật thiết kế ngôn ngữ yêu cầu các cuộc gọi phương thức / quyền truy cập thành viên để sử dụng một .toán tử, mặc dù đó là một quy ước ngày càng phổ biến. Bạn có biết rằng Smalltalk không có toán tử gọi phương thức? Sự kết hợp đơn giản object methodlà đủ.
amon

20
Python thực hiện quá tải trừ, để đặt trừ (và nó cũng có thể bị quá tải trong các loại do người dùng định nghĩa). Các bộ Python cũng quá tải hầu hết các toán tử bitwise cho giao lộ / union / vv.
Kevin

Câu trả lời:


116

Nói tóm lại, không có bất kỳ phép toán nào giống như phép trừ đặc biệt hữu ích trên các chuỗi mà mọi người muốn viết thuật toán.

Các +nhà điều hành thường biểu thị hoạt động của một chất phụ gia monoid , nghĩa là một hoạt động kết hợp với một yếu tố bản sắc:

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

Thật hợp lý khi sử dụng toán tử này cho những thứ như cộng số nguyên, nối chuỗi và đặt liên kết vì tất cả chúng đều có cùng cấu trúc đại số:

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

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

Và chúng ta có thể sử dụng nó để viết các thuật toán tiện dụng giống như một concathàm hoạt động trên một chuỗi của bất kỳ thứ gì có thể ghép nối được, ví dụ:

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

Khi phép trừ -được tham gia, bạn thường nói về cấu trúc của một nhóm , trong đó thêm một nghịch đảo −A cho mọi phần tử A, sao cho:

  • A + A = A + A = 0

Và mặc dù điều này có ý nghĩa đối với những thứ như phép trừ số nguyên và dấu phẩy động, hoặc thậm chí đặt sự khác biệt, nhưng nó không có ý nghĩa nhiều cho chuỗi và danh sách. Nghịch đảo là "foo"gì?

Có một cấu trúc được gọi là monoid hủy , không có nghịch đảo, nhưng có thuộc tính hủy , do đó:

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

Đây là cấu trúc bạn mô tả, ở đâu "ab" - "b" == "a", nhưng "ab" - "c"không được xác định. Chỉ là chúng ta không có nhiều thuật toán hữu ích sử dụng cấu trúc này. Tôi đoán nếu bạn nghĩ rằng ghép nối là tuần tự hóa, thì phép trừ có thể được sử dụng cho một số loại phân tích cú pháp.


2
Đối với phép trừ tập hợp (và nhiều tập hợp) có ý nghĩa, bởi vì không giống như các chuỗi, thứ tự của phần tử không quan trọng.
CodeInChaos

@CodesInChaos: Tôi đã thêm một đề cập đến chúng, nhưng tôi không thực sự thoải mái khi đặt các bộ làm ví dụ về một nhóm nhóm Tôi không tin rằng chúng tạo thành một nhóm, vì bạn thường không thể xây dựng nghịch đảo của một bộ.
Jon Purdy

12
Trên thực tế, +hoạt động cũng giao hoán cho các con số, nghĩa là A+B == B+A, điều này làm cho nó trở thành một ứng cử viên tồi cho nối chuỗi. Điều này, cộng với ưu tiên toán tử gây nhầm lẫn biểu hiện bằng cách sử dụng +để nối chuỗi một lỗi lịch sử. Tuy nhiên, sự thật là việc sử dụng -cho bất kỳ hoạt động chuỗi nào đã khiến mọi thứ trở nên tồi tệ hơn nhiều
Holger

2
@Darkhogg: Phải! PHP mượn .từ Perl; Nó ~ở Perl6, có thể là những người khác.
Jon Purdy

1
@MartinBeckett nhưng bạn có thể thấy rằng hành vi có thể gây nhầm lẫn với .text.gz.text...
Boris the Spider

38

Bởi vì nối hai chuỗi hợp lệ bất kỳ luôn luôn là một hoạt động hợp lệ, nhưng điều ngược lại là không đúng.

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

Nên a - bở đây làm gì? Thực sự không có cách nào tốt để trả lời câu hỏi đó, vì bản thân câu hỏi không hợp lệ.


31
@DigvijayYadav, nếu bạn loại bỏ 5 quả xoài khỏi 5 quả táo thì có phải là một quầy của -5 quả xoài không? Nó có làm gì không? Bạn có thể định nghĩa điều này đủ tốt để nó có thể được chấp nhận rộng rãi và đưa vào tất cả các trình biên dịch và phiên dịch ngôn ngữ để sử dụng toán tử này trong biểu mẫu này không? Đó là thách thức lớn ở đây.
JB King

28
@DigvijayYadav: Vì vậy, bạn vừa mô tả hai cách có thể để thực hiện điều này và có một lý lẽ tốt để coi mỗi cách là hợp lệ, vì vậy chúng tôi đã tạo ra một mớ hỗn độn về ý tưởng chỉ định thao tác này. : P
Mason Wheeler

13
@smci Dường như với tôi 5 + Falserõ ràng là một lỗi , vì một số không phải là boolean và boolean không phải là một số.
Mason Wheeler

6
@JanDvorak: Không có gì đặc biệt là "Haskelly" về điều đó; đó là kiểu gõ mạnh cơ bản.
Mason Wheeler

5
@DigvijayYadav Vì vậy (a+b)-b = a(hy vọng!), Nhưng (a-b)+bđôi khi a, đôi khi a+bphụ thuộc vào việc có phải blà một chuỗi con của ahay không? Cái quái gì thế này?

28

Bởi vì -toán tử cho thao tác chuỗi không có đủ "sự gắn kết ngữ nghĩa". Các toán tử chỉ nên bị quá tải khi hoàn toàn rõ ràng những gì quá tải làm với toán hạng của nó và phép trừ chuỗi không đáp ứng thanh đó.

Do đó, các cuộc gọi phương thức được ưu tiên:

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

Trong ngôn ngữ C #, chúng tôi sử dụng +để nối chuỗi vì biểu mẫu

var result = string1 + string2 + string3;

thay vì

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

thuận tiện và dễ đọc hơn, mặc dù một lời gọi hàm có lẽ "chính xác" hơn từ quan điểm ngữ nghĩa.

Các +nhà điều hành thực sự chỉ có thể có nghĩa là một điều trong bối cảnh này. Đây không phải là đúng đối với -, kể từ khi khái niệm về chuỗi trừ là mơ hồ (gọi hàm Replace(source, oldValue, newValue)với ""newValuetham số loại bỏ tất cả nghi ngờ, và các chức năng có thể được sử dụng để thay đổi chuỗi con, không chỉ loại bỏ chúng).

Tất nhiên, vấn đề là sự quá tải của toán tử phụ thuộc vào các loại được truyền cho toán tử và nếu bạn truyền một chuỗi có số phải có, bạn có thể nhận được kết quả mà bạn không mong đợi. Ngoài ra, đối với nhiều phép nối (nghĩa là trong một vòng lặp), một StringBuilderđối tượng được ưu tiên hơn, vì mỗi lần sử dụng +tạo ra một chuỗi hoàn toàn mới và hiệu suất có thể bị ảnh hưởng. Vì vậy, các +nhà điều hành thậm chí không thích hợp trong tất cả các bối cảnh.

Có các quá tải toán tử có độ kết dính ngữ nghĩa tốt hơn +toán tử thực hiện cho nối chuỗi. Đây là một số cộng hai số phức:

public static Complex operator +(Complex c1, Complex c2) 
{
    return new Complex(c1.real + c2.real, c1.imaginary + c2.imaginary);
}

8
+1 Cho hai chuỗi, A và B, tôi có thể nghĩ AB là "xóa dấu B ở cuối A", "xóa một thể hiện của B khỏi một nơi nào đó trong A", "xóa tất cả các thể hiện của B khỏi một nơi nào đó trong A , "hoặc thậm chí" xóa tất cả các ký tự được tìm thấy trong B khỏi A. "
Cort Ammon

8

Các ngôn ngữ Groovy không cho phép -:

println('ABC'-'B')

trả về:

AC

Và:

println( 'Hello' - 'World' )

trả về:

Hello

Và:

println('ABABABABAB' - 'B')

trả về:

AABABABAB

11
Thú vị - vì vậy nó chọn để loại bỏ sự xuất hiện đầu tiên? Một ví dụ tốt cho một hành vi hoàn toàn phản trực giác.
Hulk

9
Do đó, chúng ta có ('ABABABABA' + 'B') - 'B'giá trị không giống với giá trị bắt đầu 'ABABABABA'.
một CVn

3
@ MichaelKjorling OTOH, (A + B) - A == Bvới mọi A và B. Tôi có thể gọi đó là phép trừ trái không?
John Dvorak

2
Haskell đã ++cho ghép. Nó hoạt động trên bất kỳ danh sách nào và một chuỗi chỉ là một danh sách các ký tự. Nó cũng có \\, loại bỏ sự xuất hiện đầu tiên của mọi phần tử trong đối số bên phải khỏi đối số bên trái.
John Dvorak

3
Tôi cảm thấy như những ví dụ này chính xác là lý do tại sao không nên có toán tử trừ cho chuỗi. Đó là hành vi không nhất quán và không trực quan. Khi tôi nghĩ về "-" Tôi chắc chắn không nghĩ ", hãy xóa phiên bản đầu tiên của chuỗi khớp, nếu nó xảy ra, nếu không thì không làm gì cả."
enderland

6

Dấu cộng có thể có ý nghĩa theo ngữ cảnh trong nhiều trường hợp, nhưng một ví dụ ngược lại (có lẽ là một ngoại lệ chứng minh quy tắc) trong Python là đối tượng được thiết lập, cung cấp cho -nhưng không +:

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

Không có ý nghĩa gì khi sử dụng +dấu hiệu vì ý định có thể mơ hồ - điều đó có nghĩa là đặt giao lộ hoặc kết hợp? Thay vào đó, nó sử dụng |cho liên minh và &cho giao lộ:

>>> set('abc') | set('bcd')
set(['a', 'c', 'b', 'd'])
>>> set('abc') & set('bcd')
set(['c', 'b'])

2
Điều này có nhiều khả năng vì phép trừ tập hợp được xác định trong toán học, nhưng phép cộng tập hợp thì không.
Mehrdad

Việc sử dụng "-" có vẻ tinh ranh; những gì thực sự cần thiết là một toán tử "nhưng không" cũng sẽ hữu ích khi thực hiện số học bitwise với các số nguyên. Nếu 30 ~ & 7 là 24, thì sử dụng ~ & với các bộ sẽ phù hợp độc đáo với & và | mặc dù các bộ thiếu một toán tử ~.
supercat

1
set('abc') ^ set('bcd')trả về set(['a', 'd']), nếu bạn hỏi về sự khác biệt đối xứng.
Aaron Hall

3

" -" Được sử dụng trong một số từ ghép (ví dụ: "tại chỗ") để nối các phần khác nhau vào cùng một từ. Tại sao chúng ta không sử dụng " -" để nối các chuỗi khác nhau lại với nhau trong các ngôn ngữ lập trình? Tôi nghĩ rằng nó sẽ có ý nghĩa hoàn hảo! Đến địa ngục với điều +vô nghĩa này !

Tuy nhiên, chúng ta hãy thử nhìn điều này từ một góc độ trừu tượng hơn một chút.

Làm thế nào bạn sẽ xác định đại số chuỗi? Những hoạt động nào bạn sẽ có, và luật nào sẽ giữ cho họ? Quan hệ của họ sẽ là gì?

Hãy nhớ rằng, có thể hoàn toàn không có sự mơ hồ! Mọi trường hợp có thể phải được xác định rõ, ngay cả khi điều đó có nghĩa là không thể làm điều này! Đại số của bạn càng nhỏ, điều này càng dễ thực hiện.

Ví dụ, việc thêm hoặc bớt hai chuỗi có nghĩa là gì?

Nếu bạn thêm hai chuỗi (ví dụ: let a = "aa"b = "bb"), bạn sẽ nhận được aabbkết quả của a + b?

Thế còn b + a? Đó sẽ là bbaa? Tại sao không aabb? Điều gì xảy ra nếu bạn trừ đi aakết quả của sự bổ sung của bạn? Chuỗi của bạn sẽ có một khái niệm về số lượng âm aatrong đó?

Bây giờ hãy quay lại từ đầu của câu trả lời này và thay thế bằng spaceshuttlechuỗi. Để khái quát, tại sao bất kỳ hoạt động được xác định hoặc không được xác định cho bất kỳ loại?

Điểm tôi đang cố gắng đưa ra là, không có gì ngăn cản bạn tạo ra đại số cho bất cứ điều gì. Có thể khó tìm thấy các hoạt động có ý nghĩa, hoặc thậm chí các hoạt động hữu ích cho nó.

Đối với các chuỗi, nối là khá nhiều thứ hợp lý duy nhất tôi từng gặp. Không quan trọng biểu tượng nào được sử dụng để thể hiện hoạt động.


1
"Đối với các chuỗi, nối là khá nhiều thứ hợp lý duy nhất tôi từng gặp" . Sau đó, bạn có không đồng ý với Python 'xy' * 3 == 'xyxyxy'?
smci

3
@smci đó chỉ là phép nhân-lặp đi lặp lại , chắc chắn?
jonrsharpe

toán tử thích hợp để nối tàu vũ trụ là gì?
Mr.Mindor

4
@ Mr.Mindor backspace ... để xóa khoảng trống giữa các tàu vũ trụ.
YoungJohn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.