Tại sao Ruby không hỗ trợ i ++ hoặc i-- (toán tử tăng / giảm)?


130

Toán tử tăng / giảm trước / sau ( ++--) là cú pháp ngôn ngữ lập trình khá chuẩn (ít nhất là đối với các ngôn ngữ hướng thủ tục và hướng đối tượng).

Tại sao Ruby không hỗ trợ họ? Tôi hiểu bạn có thể thực hiện điều tương tự với +=-=, nhưng nó chỉ có vẻ kỳ quặc tùy ý để loại trừ một cái gì đó như thế, đặc biệt là kể từ khi nó rất súc tích và truyền thống.

Thí dụ:

i = 0    #=> 0
i += 1   #=> 1
i        #=> 1
i++      #=> expect 2, but as far as I can tell, 
         #=> irb ignores the second + and waits for a second number to add to i

Tôi hiểu Fixnumlà bất biến, nhưng nếu +=chỉ có thể tạo ra một cái mới Fixnumvà thiết lập nó, tại sao không làm điều tương tự cho ++?

Là sự nhất quán trong các bài tập có chứa =nhân vật là lý do duy nhất cho việc này, hoặc tôi đang thiếu một cái gì đó?


2
Grep mã nguồn ruby ​​cho các nhà khai thác như vậy. Nếu không có ai - Matz không thích họ.
Eimantas

Bạn không thể thực hiện giao dịch với +=nhà điều hành. Trong CI cố gắng sử dụng ++/ --chỉ bên trong các điều kiện, ưu tiên cho nghĩa đen hơn +=/ -=trong một tuyên bố cơ bản. Có lẽ bởi vì tôi đã học Python (rất lâu sau C mặc dù ...)
Nick T

Không có câu hỏi nào như thế này cho Python chỉ mới hôm qua sao?
BoltClock

@Eimantas rõ ràng là người tạo ra ngôn ngữ không thích họ. Nó quá phổ biến để bỏ qua. Tôi đã tự hỏi TẠI SAO, điều này đã phần nào được làm rõ bằng các câu trả lời dưới đây.
Andy_Vulhop

1
Tôi nghĩ rằng đây là (gần như) một câu hỏi SO mẫu. Nó không phải là một cái gì đó không dễ dàng để có được một câu trả lời được xem xét. Nó khá rõ ràng và cụ thể trong câu trả lời nào là bắt buộc và câu trả lời làm sáng tỏ khía cạnh lập trình có thể khiến người ta suy nghĩ rộng hơn là cốt lõi của câu hỏi.
PurplePilot

Câu trả lời:


97

Đây là cách Matz (Yukihiro Matsumoto) giải thích nó trong một chủ đề cũ :

Hi,

In message "[ruby-talk:02706] X++?"
    on 00/05/10, Aleksi Niemelä <aleksi.niemela@cinnober.com> writes:

|I got an idea from http://www.pragprog.com:8080/rubyfaq/rubyfaq-5.html#ss5.3
|and thought to try. I didn't manage to make "auto(in|de)crement" working so
|could somebody help here? Does this contain some errors or is the idea
|wrong?

  (1) ++ and -- are NOT reserved operator in Ruby.

  (2) C's increment/decrement operators are in fact hidden assignment.
      They affect variables, not objects.  You cannot accomplish
      assignment via method.  Ruby uses +=/-= operator instead.

  (3) self cannot be a target of assignment.  In addition, altering
      the value of integer 1 might cause severe confusion throughout
      the program.

                            matz.

10
2 và 3 có vẻ mâu thuẫn. Nếu tự gán là xấu, tại sao +=/ -=ok? Và sẽ không 1+=1tệ như vậy sao? ( syntax error, unexpected ASSIGNMENT
Thất

2
(2) có nghĩa là trong C, bạn không tự thay đổi giá trị ... bạn đang thay đổi nội dung của biến chứa giá trị. Đó là một chút quá meta cho bất kỳ ngôn ngữ đi qua giá trị. Trừ khi có cách để chuyển một cái gì đó bằng tham chiếu trong Ruby (và ý tôi thực sự là "bằng cách tham chiếu", không chuyển tham chiếu theo giá trị), việc thay đổi chính biến đó sẽ không thể thực hiện được trong một phương thức.
cHao

5
Có lẽ tôi đang thiếu một cái gì đó ở đây. +=thay thế đối tượng các tham chiếu biến bằng một đối tượng hoàn toàn mới. Bạn có thể kiểm tra điều này bằng cách gọi i.object_idtrước và sau i+=1. Tại sao điều đó sẽ khó khăn hơn về mặt kỹ thuật để làm với ++?
Andy_Vulhop

6
@Andy_Vulhop: # 3 đang giải thích lý do tại sao về mặt kỹ thuật không thể gán là một phương thức, chứ không phải tại sao nói chung việc chuyển nhượng là không thể (áp phích Matz đã trả lời vì nghĩ rằng có thể tạo ra một ++phương thức).
Chuck

2
Trong Ruby tất cả các nghĩa đen cũng là đối tượng. Vì vậy, tôi tin rằng Matz đang cố gắng nói rằng anh ta không chắc chắn rằng anh ta thích ý tưởng xử lý 1 ++ như một tuyên bố. Cá nhân tôi nghĩ rằng điều này là không hợp lý vì như @Andy_Vulhop nói 1 + = 2 giống như wack, và Ruby chỉ phát sinh lỗi khi bạn làm điều này. Vì vậy, 1 ++ không khó để xử lý. Có lẽ các nhà phân tích cần phải đối phó với loại đường cú pháp đó là không mong muốn.
Steve Midgley

28

Một lý do là cho đến nay mọi toán tử gán (tức là toán tử thay đổi một biến) đều có một toán tử =. Nếu bạn thêm ++--, đó không còn là trường hợp nữa.

Một lý do khác là hành vi ++--thường gây nhầm lẫn cho mọi người. Case in point: Giá trị trả về i++trong ví dụ của bạn thực sự sẽ là 1, không phải 2 ( ituy nhiên giá trị mới sẽ là 2).


4
Hơn bất kỳ lý do nào khác cho đến nay, lý do "tất cả các bài tập đều có =trong đó" dường như có ý nghĩa. Tôi có thể sắp xếp sự tôn trọng đó như một sự tuân thủ quyết liệt với tính nhất quán.
Andy_Vulhop

những gì về điều này: a.capitalize! (phân công ngầm định của a)
Luís Soares

1
@ LuísSoares a.capitalize!không gán lại a, nó sẽ biến đổi chuỗi atham chiếu. Các tham chiếu khác cho cùng một chuỗi sẽ bị ảnh hưởng và nếu bạn thực hiện a.object_idtrước và sau cuộc gọi đến capitalize, bạn sẽ nhận được kết quả tương tự (điều đó sẽ không đúng nếu bạn a = a.capitalizethay thế).
sepp2k

1
@ LuísSoares Như tôi đã nói, a.capitalize!sẽ ảnh hưởng đến các tham chiếu khác đến cùng chuỗi. Đó thực sự là một sự khác biệt thực tế. Ví dụ: nếu bạn có def yell_at(name) name.capitalize!; puts "HEY, #{name}!" endvà sau đó bạn gọi nó như thế này : my_name = "luis"; yell_at(my_name), giá trị của my_namebây giờ sẽ là "LUIS", trong khi nó sẽ không bị ảnh hưởng nếu bạn đã sử dụng capitalizevà chuyển nhượng.
sepp2k

1
Ồ Điều đó thật đáng sợ ... Biết rằng trong các chuỗi Java là bất biến .. Nhưng với sức mạnh là trách nhiệm. Cảm ơn đã giải thích.
Luís Soares

25

Nó không thông thường trong các ngôn ngữ OO. Trên thực tế, không có ++trong Smalltalk, ngôn ngữ đặt ra thuật ngữ "lập trình hướng đối tượng" (và ngôn ngữ mà Ruby chịu ảnh hưởng mạnh mẽ nhất). Điều bạn muốn nói là thông thường trong C và các ngôn ngữ bắt chước chặt chẽ C. Ruby có một cú pháp giống như C, nhưng nó không hề phù hợp với việc tuân thủ các truyền thống C.

Về lý do tại sao nó không có trong Ruby: Matz không muốn nó. Đó thực sự là lý do cuối cùng.

Lý do không có thứ đó tồn tại trong Smalltalk là vì đó là một phần của triết lý vượt trội của ngôn ngữ, việc gán một biến về cơ bản là một loại khác với việc gửi tin nhắn đến một đối tượng - nó ở một cấp độ khác. Suy nghĩ này có lẽ đã ảnh hưởng đến Matz trong việc thiết kế Ruby.

Sẽ không thể đưa nó vào Ruby - bạn có thể dễ dàng viết một bộ tiền xử lý biến đổi tất cả ++thành +=1. nhưng rõ ràng Matz không thích ý tưởng về một nhà điều hành đã thực hiện một "nhiệm vụ ẩn". Cũng có một chút lạ khi có một toán tử với toán hạng nguyên ẩn bên trong nó. Không có toán tử khác trong ngôn ngữ hoạt động theo cách đó.


1
Tôi không nghĩ rằng đề xuất tiền xử lý của bạn sẽ làm việc; (không phải là chuyên gia) nhưng tôi nghĩ rằng i = 42, i ++ sẽ trả về 42 trong đó i + = 1 sẽ trả về 43. Tôi có sai trong trường hợp này không? Vì vậy, đề nghị của bạn trong trường hợp đó là sử dụng i ++ vì ++ tôi thường được sử dụng, điều này khá tệ và có thể gây hại nhiều hơn là tốt.
AturSams

12

Tôi nghĩ có một lý do khác: ++trong Ruby sẽ không hữu ích từ xa như trong C và những người kế thừa trực tiếp của nó.

Lý do là, fortừ khóa: mặc dù nó rất cần thiết trong C, nhưng nó hầu như không cần thiết trong Ruby. Hầu hết các lần lặp trong Ruby được thực hiện thông qua vô số phương thức, chẳng hạn như eachmapkhi lặp qua một số cấu trúc dữ liệu và Fixnum#timesphương thức, khi bạn cần lặp một số lần chính xác.

Trên thực tế, theo như tôi đã thấy, phần lớn thời gian +=1được sử dụng bởi những người mới di cư sang Ruby từ các ngôn ngữ kiểu C.

Nói tóm lại, thật sự rất đáng nghi nếu các phương thức ++--sẽ được sử dụng.


1
Đây là câu trả lời tốt nhất imho. ++ thường được sử dụng để lặp lại. Ruby không khuyến khích kiểu lặp này.
AturSams

3

Tôi nghĩ lý do của Matz không thích chúng là nó thực sự thay thế biến bằng một cái mới.

Ví dụ:

a = someClass.new
def a.go
  'xin chào'
kết thúc
# tại thời điểm này, bạn có thể gọi a.go
# nhưng nếu bạn đã làm một ++
# điều đó thực sự có nghĩa là a = a + 1
# vì vậy bạn không còn có thể gọi a.go
# như bạn đã mất bản gốc của bạn

Bây giờ nếu ai đó có thể thuyết phục anh ta rằng nó chỉ nên gọi #succ! hoặc những gì không, điều đó sẽ có ý nghĩa hơn, và tránh vấn đề. Bạn có thể đề nghị nó trên lõi ruby.


9
"Bạn có thể đề xuất nó trên lõi ruby" ... Sau khi bạn đã đọc hiểu các đối số trong tất cả các luồng khác, nơi nó được đề xuất lần trước, và thời gian trước đó, và thời gian trước đó, và thời gian trước đó, và thời gian trước đó, và ... Tôi đã không ở trong cộng đồng Ruby rất lâu, nhưng chỉ trong thời gian của tôi, tôi nhớ ít nhất hai mươi cuộc thảo luận như vậy.
Jörg W Mittag

3

Bạn có thể xác định .+toán tử tự tăng:

class Variable
  def initialize value = nil
    @value = value
  end
  attr_accessor :value
  def method_missing *args, &blk
    @value.send(*args, &blk)
  end
  def to_s
    @value.to_s
  end

  # pre-increment ".+" when x not present
  def +(x = nil)
    x ? @value + x : @value += 1
  end
  def -(x = nil)
    x ? @value - x : @value -= 1
  end
end

i = Variable.new 5
puts i                #=> 5

# normal use of +
puts i + 4            #=> 9
puts i                #=> 5

# incrementing
puts i.+              #=> 6
puts i                #=> 6

Thông tin thêm về "Biến lớp" có sẵn trong " Biến đối với lớp đối tượng Fixnum ".


2

Và theo lời của David Black từ cuốn sách "The Ruby-Grounded Rubyist":

Một số đối tượng trong Ruby được lưu trữ trong các biến dưới dạng giá trị ngay lập tức. Chúng bao gồm các số nguyên, ký hiệu (trông giống như: này) và các đối tượng đặc biệt đúng, sai và không. Khi bạn gán một trong các giá trị này cho một biến (x = 1), biến đó giữ chính giá trị đó, thay vì tham chiếu đến nó. Về mặt thực tế, điều này không thành vấn đề (và nó thường sẽ được để lại như ngụ ý, thay vì được đánh vần nhiều lần, trong các cuộc thảo luận về tài liệu tham khảo và các chủ đề liên quan trong cuốn sách này). Ruby tự động xử lý hội thảo về các tham chiếu đối tượng; bạn không phải thực hiện thêm bất kỳ công việc nào để gửi tin nhắn đến một đối tượng có chứa một tham chiếu đến một chuỗi, trái ngược với một đối tượng có chứa một giá trị nguyên ngay lập tức. Nhưng quy tắc đại diện giá trị ngay lập tức có một vài phân nhánh thú vị, đặc biệt là khi nói đến số nguyên. Đối với một điều, bất kỳ đối tượng nào được biểu thị như một giá trị ngay lập tức luôn luôn chính xác là cùng một đối tượng, bất kể nó được gán cho bao nhiêu biến. Chỉ có một đối tượng 100, chỉ có một đối tượng sai, v.v. Bản chất ngay lập tức, duy nhất của các biến liên kết số nguyên nằm sau việc thiếu các toán tử tăng trước và sau tăng của Ruby, nghĩa là, bạn không thể làm điều này trong Ruby: x = 1 x ++ # Không có toán tử nào như vậy Lý do là do với sự hiện diện ngay lập tức của 1 trong x, x ++ sẽ giống như 1 ++, điều đó có nghĩa là bạn sẽ thay đổi số 1 thành số 2 và điều đó thật vô nghĩa. bất kể nó được gán cho bao nhiêu biến. Chỉ có một đối tượng 100, chỉ có một đối tượng sai, v.v. Bản chất ngay lập tức, duy nhất của các biến liên kết số nguyên nằm sau việc thiếu các toán tử tăng trước và sau tăng của Ruby, nghĩa là, bạn không thể làm điều này trong Ruby: x = 1 x ++ # Không có toán tử nào như vậy Lý do là do với sự hiện diện ngay lập tức của 1 trong x, x ++ sẽ giống như 1 ++, điều đó có nghĩa là bạn sẽ thay đổi số 1 thành số 2 và điều đó thật vô nghĩa. bất kể nó được gán cho bao nhiêu biến. Chỉ có một đối tượng 100, chỉ có một đối tượng sai, v.v. Bản chất ngay lập tức, duy nhất của các biến liên kết số nguyên nằm sau việc thiếu các toán tử tăng trước và sau tăng của Ruby, nghĩa là, bạn không thể làm điều này trong Ruby: x = 1 x ++ # Không có toán tử nào như vậy Lý do là do với sự hiện diện ngay lập tức của 1 trong x, x ++ sẽ giống như 1 ++, điều đó có nghĩa là bạn sẽ thay đổi số 1 thành số 2 và điều đó thật vô nghĩa.


Nhưng tại sao bạn có thể làm "1.tiếp theo"?
Magne

1

Không thể đạt được điều này bằng cách thêm một phương thức mới vào lớp fixnum hoặc Integer?

$ ruby -e 'numb=1;puts numb.next'

trả về 2

Các phương thức "Phá hủy" dường như được thêm vào !để cảnh báo người dùng có thể, vì vậy việc thêm một phương thức mới được gọi next!sẽ thực hiện khá nhiều những gì được yêu cầu tức là.

$ ruby -e 'numb=1; numb.next!; puts numb' 

trả về 2 (vì tê đã được tăng lên)

Tất nhiên, next!phương thức sẽ phải kiểm tra xem đối tượng có phải là biến nguyên không phải là số thực, nhưng điều này nên có sẵn.


1
Integer#nextđã tồn tại (nhiều hơn hoặc ít hơn), ngoại trừ nó được gọi Integer#succthay thế (cho 'người kế vị'). Nhưng Integer#next!(hoặc Integer#succ!) sẽ là vô nghĩa: hãy nhớ rằng các phương thức hoạt động trên các đối tượng , không phải là các biến , do đó numb.next!sẽ chính xác bằng 1.next!, nghĩa là, nó sẽ biến đổi 1 bằng 2 . ++sẽ tốt hơn một chút vì nó có thể là đường cú pháp cho một bài tập, nhưng cá nhân tôi thích cú pháp hiện tại trong đó tất cả các bài tập được thực hiện =.
triết học

Để hoàn thành nhận xét trên: và Integer#predđể lấy lại tiền thân.
Yoni

-6

Kiểm tra các toán tử này từ gia đình C trong irb của Ruby và tự mình kiểm tra chúng:

x = 2    # x is 2
x += 2   # x is 4
x++      # x is now 8
++x      # x reverse to 4

3
Điều này rõ ràng là sai và không hoạt động, như (x++)là một tuyên bố không hợp lệ trong Ruby.
Anothermh
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.