เหตุใด Ruby จึงไม่รองรับ i ++ หรือ i - (ตัวดำเนินการเพิ่ม / ลด)


130

ตัวดำเนินการเพิ่ม / ลดก่อน / หลัง ( ++และ--) เป็นไวยากรณ์ภาษาโปรแกรมมาตรฐานที่ค่อนข้างดี (สำหรับภาษาขั้นตอนและภาษาเชิงวัตถุอย่างน้อย)

ทำไม Ruby ถึงไม่สนับสนุนพวกเขา? ฉันเข้าใจว่าคุณสามารถทำในสิ่งเดียวกันได้ด้วย+=และ-=แต่มันก็ดูเป็นเรื่องแปลกที่จะไม่รวมบางสิ่งเช่นนั้นโดยพลการโดยเฉพาะอย่างยิ่งเนื่องจากมันกระชับและเป็นแบบแผน

ตัวอย่าง:

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

ผมเข้าใจว่าFixnumจะไม่เปลี่ยนรูป แต่ถ้า+=สามารถ instanciate ใหม่Fixnumและตั้งทำไมไม่ทำเช่นเดียวกันสำหรับ++?

ความสม่ำเสมอในการมอบหมายงานที่มี=ตัวละครเป็นเหตุผลเดียวสำหรับสิ่งนี้หรือฉันพลาดอะไรไป?


2
ซอร์สโค้ด Grep Ruby สำหรับตัวดำเนินการดังกล่าว ถ้าไม่มี - Matz ไม่ชอบพวกเขา
Eimantas

คุณไม่สามารถทำ preincrement กับ+=โอเปอเรเตอร์ได้ ใน CI พยายามใช้++/ --only inside conditionals โดยเลือกใช้คำสั่งตามตัวอักษร+=/ -=ในคำสั่งพื้นฐานมากกว่า อาจเป็นเพราะฉันเรียนรู้ Python (นานหลัง C แม้ว่า ... )
Nick T

เมื่อวานไม่มีคำถามแบบนี้กับ Python ใช่หรือไม่?
BoltClock

@Eimantas เห็นได้ชัดว่าผู้สร้างภาษาไม่ชอบพวกเขา เป็นเรื่องธรรมดาเกินไปที่จะมองข้าม ฉันสงสัยว่าทำไมซึ่งได้รับการชี้แจงจากคำตอบด้านล่าง
Andy_Vulhop

1
ฉันคิดว่านี่ (เกือบ) เป็นคำถามแบบ SO ไม่ใช่สิ่งที่ Google ไม่สามารถหาคำตอบได้โดยง่าย ค่อนข้างชัดเจนและเฉพาะเจาะจงในสิ่งที่ต้องใช้คำตอบและคำตอบนั้นให้ความกระจ่างในแง่มุมของการเขียนโปรแกรมที่สามารถทำให้คนคิดได้กว้างขึ้นมากกว่าแค่ประเด็นหลักของคำถาม
PurplePilot

คำตอบ:


97

นี่คือวิธีที่ Matz (Yukihiro Matsumoto) อธิบายไว้ในกระทู้เก่า:

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 และ 3 ดูเหมือนขัดแย้งกัน ถ้าการมอบหมายงานตัวเองไม่ดีทำไม+=/ -=ตกลง? และจะไม่1+=1เลวร้ายเท่า? (มันล้มเหลวใน IRB ด้วยsyntax error, unexpected ASSIGNMENT)
Andy_Vulhop

2
(2) หมายความว่าใน C คุณไม่ได้เปลี่ยนค่าเอง ... คุณกำลังแก้ไขเนื้อหาของตัวแปรที่เก็บค่าไว้ นั่นเป็น meta เกินไปสำหรับภาษาใด ๆ ที่ส่งผ่านค่า เว้นแต่จะมีวิธีส่งผ่านบางสิ่งโดยการอ้างอิงใน Ruby (และฉันหมายถึง "โดยอ้างอิง" อย่างแท้จริงโดยไม่ส่งการอ้างอิงตามค่า) การแก้ไขตัวแปรเองจะไม่สามารถทำได้ภายในวิธีการ
cHao

5
บางทีฉันอาจจะขาดอะไรบางอย่างที่นี่ +=แทนที่อ็อบเจ็กต์ที่ตัวแปรอ้างอิงด้วยอ็อบเจ็กต์ใหม่ทั้งหมด คุณสามารถตรวจสอบโดยการเรียกก่อนและหลังi.object_id i+=1เหตุใดจึงเป็นเรื่องยุ่งยากอีกต่อไปที่จะทำกับ++?
Andy_Vulhop

6
@Andy_Vulhop: # 3 กำลังอธิบายว่าเหตุใดจึงเป็นไปไม่ได้ในทางเทคนิคที่การมอบหมายให้เป็นวิธีการไม่ใช่ว่าทำไมการมอบหมายงานจึงเป็นไปไม่ได้โดยทั่วไป (Matz ผู้โพสต์ตอบกลับว่าคิดว่าอาจเป็นไปได้ที่จะสร้าง++วิธีการ)
Chuck

2
ใน Ruby ตัวอักษรทั้งหมดยังเป็นวัตถุ ดังนั้นฉันเชื่อว่า Matz พยายามจะบอกว่าเขาไม่แน่ใจว่าเขาชอบแนวคิดในการจัดการกับ 1 ++ เป็นคำสั่ง โดยส่วนตัวฉันคิดว่านี่ไม่มีเหตุผลเนื่องจาก @Andy_Vulhop กล่าวว่า 1 + = 2 นั้นเหมือนกับ wack และ Ruby ก็ทำให้เกิดข้อผิดพลาดเมื่อคุณทำสิ่งนี้ 1 ++ จึงไม่ยากที่จะจัดการ อาจเป็นไปได้ว่าความต้องการของผู้แยกวิเคราะห์ในการรับมือกับน้ำตาลที่มีปฏิกิริยาแบบนั้นเป็นสิ่งที่ไม่พึงปรารถนา
Steve Midgley

28

เหตุผลประการหนึ่งก็คือจนถึงตอนนี้ตัวดำเนินการกำหนดทุกตัว (เช่นตัวดำเนินการที่เปลี่ยนตัวแปร) มี=อยู่ในตัว หากคุณเพิ่ม++และ--จะไม่เป็นเช่นนั้นอีกต่อไป

อีกสาเหตุหนึ่งคือพฤติกรรมของ++และ--มักจะสร้างความสับสนให้กับผู้คน ตรงประเด็น: ค่าส่งคืนi++ในตัวอย่างของคุณจริง ๆ แล้วจะเป็น 1 ไม่ใช่ 2 (ค่าใหม่iจะเป็น 2 อย่างไรก็ตาม)


4
มากกว่าเหตุผลอื่นใดเหตุผลที่ว่า "งานทั้งหมดมี=อยู่ในนั้น" ดูเหมือนจะสมเหตุสมผล ฉันสามารถให้ความเคารพได้ว่าเป็นการยึดมั่นอย่างจริงจังในความสม่ำเสมอ
Andy_Vulhop

แล้วสิ่งนี้: a.capitalize! (การมอบหมายโดยปริยายของ a)
Luís Soares

1
@ LuísSoares a.capitalize!ไม่ได้กำหนดใหม่aมันจะกลายพันธุ์สตริงที่aอ้างถึง การอ้างอิงอื่น ๆ ไปยังสตริงเดียวกันจะได้รับผลกระทบและหากคุณทำa.object_idก่อนและหลังการโทรcapitalizeคุณจะได้รับผลลัพธ์เดียวกัน (ซึ่งทั้งสองอย่างนี้จะไม่เป็นจริงหากคุณทำa = a.capitalizeแทน)
sepp2k

1
@ LuísSoaresอย่างที่บอกa.capitalize!จะมีผลต่อการอ้างอิงอื่น ๆ ในสตริงเดียวกัน นั่นคือความแตกต่างในทางปฏิบัติอย่างมาก ตัวอย่างเช่นถ้าคุณมีdef yell_at(name) name.capitalize!; puts "HEY, #{name}!" endแล้วคุณเรียกสิ่งนี้my_name = "luis"; yell_at(my_name)ว่ามูลค่าของmy_nameจะเป็นตอนนี้"LUIS"ในขณะที่มันจะไม่ได้รับผลกระทบหากคุณเคยใช้capitalizeและงานที่มอบหมาย
sepp2k

1
ว้าว. ที่น่ากลัว ... เมื่อรู้ว่าในสตริง Java นั้นไม่เปลี่ยนรูป .. แต่ด้วยอำนาจมาพร้อมความรับผิดชอบ ขอบคุณสำหรับคำอธิบาย
Luís Soares

25

ไม่ใช่ภาษา OO ทั่วไป ในความเป็นจริงไม่มี++ใน Smalltalk ซึ่งเป็นภาษาที่บัญญัติศัพท์ว่า "การเขียนโปรแกรมเชิงวัตถุ" (และภาษา Ruby ได้รับอิทธิพลมากที่สุด) สิ่งที่คุณหมายถึงคือว่ามันเป็นธรรมดาในCและภาษาอย่างใกล้ชิดเลียนแบบซีทับทิมจะมีค่อนข้าง C-เช่นไวยากรณ์ แต่มันก็ไม่ได้เลวทรามในการยึดมั่นในประเพณี C

ทำไมมันถึงไม่อยู่ใน Ruby: Matz ไม่ต้องการมัน นั่นเป็นเหตุผลสุดท้ายจริงๆ

ด้วยเหตุผลดังกล่าวไม่มีสิ่งที่มีอยู่ในสมอลล์ทอล์คเป็นเพราะมันเป็นส่วนหนึ่งของภาษาที่เอาชนะปรัชญาที่ว่าการกำหนดตัวแปรเป็นพื้นฐานที่แตกต่างกันชนิดของสิ่งกว่าการส่งข้อความไปยังวัตถุ - มันอยู่ในระดับที่แตกต่างกัน ความคิดนี้อาจมีอิทธิพลต่อ Matz ในการออกแบบ Ruby

มันเป็นไปไม่ได้ที่จะรวมไว้ใน Ruby - คุณสามารถเขียนพรีโปรเซสเซอร์ที่แปลงทั้งหมด++เป็น+=1ไฟล์. แต่เห็นได้ชัดว่า Matz ไม่ชอบแนวคิดของผู้ปฏิบัติงานที่ทำ "งานที่ซ่อนอยู่" มันดูแปลกเล็กน้อยที่มีตัวดำเนินการที่มีตัวถูกดำเนินการจำนวนเต็มซ่อนอยู่ข้างใน ไม่มีตัวดำเนินการอื่นใดในภาษาทำงานในลักษณะนั้น


1
ฉันไม่คิดว่าคำแนะนำตัวประมวลผลล่วงหน้าของคุณจะใช้ได้ผล (ไม่ใช่ผู้เชี่ยวชาญ) แต่ฉันคิดว่า i = 42, i ++ จะส่งคืน 42 โดยที่ i + = 1 จะส่งกลับ 43 ฉันไม่ถูกต้องในข้อนี้หรือไม่? ดังนั้นข้อเสนอแนะของคุณในกรณีนั้นคือให้ใช้ i ++ เพราะปกติจะใช้ ++ i ซึ่งเป็น imho ที่ไม่ดีและอาจก่อให้เกิดอันตรายมากกว่าผลดี
AturSams

12

ฉันคิดว่ามีอีกเหตุผลหนึ่ง: ++ใน Ruby จะไม่มีประโยชน์จากระยะไกลเหมือนใน C และผู้สืบทอดโดยตรง

เหตุผลคือforคีย์เวิร์ด: แม้ว่าจะมีความสำคัญใน C แต่ส่วนใหญ่จะไม่จำเป็นใน Ruby การวนซ้ำส่วนใหญ่ใน Ruby ทำได้โดยใช้วิธีการนับเช่นeachและmapเมื่อทำซ้ำผ่านโครงสร้างข้อมูลและFixnum#timesวิธีการบางอย่างเมื่อคุณต้องการวนซ้ำตามจำนวนครั้งที่แน่นอน

ที่จริงแล้วเท่าที่ฉันเห็นเวลาส่วนใหญ่+=1จะถูกใช้โดยผู้คนที่อพยพไปยัง Ruby จากภาษาแบบ C

ในระยะสั้นเป็นเรื่องที่น่าสงสัยจริงๆหากวิธีการ++และ--จะถูกนำมาใช้เลย


1
นี่คือคำตอบที่ดีที่สุด imho ++ มักใช้สำหรับการวนซ้ำ Ruby ไม่สนับสนุนการทำซ้ำประเภทนี้
AturSams

3

ฉันคิดว่าเหตุผลที่ Matz ไม่ชอบพวกเขาคือมันแทนที่ตัวแปรด้วยตัวแปรใหม่

อดีต:

a = SomeClass.new
def a.go
  'สวัสดี'
ปลาย
# ณ จุดนี้คุณสามารถโทรหา a.go
# แต่ถ้าคุณทำ ++
# นั่นหมายถึง a = a + 1 จริงๆ
# คุณจึงไม่สามารถโทรหา a.go ได้อีกต่อไป
# ในขณะที่คุณทำต้นฉบับของคุณหายไป

ตอนนี้ถ้าใครสักคนสามารถโน้มน้าวเขาได้ก็ควรเรียก #succ! หรืออะไรจะสมเหตุสมผลกว่าและหลีกเลี่ยงปัญหา คุณสามารถแนะนำได้ที่แกนทับทิม


9
"คุณสามารถแนะนำได้ที่แกนทับทิม" ... หลังจากที่คุณได้อ่านและทำความเข้าใจข้อโต้แย้งในหัวข้ออื่น ๆ ทั้งหมดที่มีการแนะนำครั้งที่แล้วและเวลาก่อนหน้านั้นและเวลาก่อนหน้านั้นและเวลาก่อนหน้านั้น และเวลาก่อนหน้านั้นและ ... ฉันไม่ได้อยู่ในชุมชน Ruby นานมากนัก แต่ในช่วงเวลาของฉันฉันจำการสนทนาเช่นนี้ได้อย่างน้อยยี่สิบครั้ง
Jörg W Mittag

3

คุณสามารถกำหนด.+ตัวดำเนินการเพิ่มเองได้:

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

ข้อมูลเพิ่มเติมเกี่ยวกับ "ตัวแปรคลาส" มีอยู่ใน " ตัวแปรคลาสเพื่อเพิ่มวัตถุ Fixnum "


2

และในคำพูดของ David Black จากหนังสือ "The Well-Grounded Rubyist":

วัตถุบางอย่างใน Ruby จะถูกเก็บไว้ในตัวแปรเป็นค่าทันที ซึ่งรวมถึงจำนวนเต็มสัญลักษณ์ (ซึ่งมีลักษณะดังนี้) และอ็อบเจกต์พิเศษจริงเท็จและศูนย์ เมื่อคุณกำหนดค่าใดค่าหนึ่งให้กับตัวแปร (x = 1) ตัวแปรจะเก็บค่านั้นเองแทนที่จะเป็นการอ้างอิง ในทางปฏิบัติสิ่งนี้ไม่สำคัญ (และมักจะถูกทิ้งไว้โดยนัยแทนที่จะสะกดซ้ำ ๆ ในการอภิปรายการอ้างอิงและหัวข้อที่เกี่ยวข้องในหนังสือเล่มนี้) Ruby จัดการการอ้างอิงวัตถุโดยอัตโนมัติ คุณไม่ต้องทำงานพิเศษใด ๆ เพื่อส่งข้อความไปยังออบเจ็กต์ที่มีการพูดการอ้างอิงถึงสตริงซึ่งตรงข้ามกับออบเจ็กต์ที่มีค่าจำนวนเต็มทันที แต่กฎการแทนค่าทันทีมีการแบ่งส่วนที่น่าสนใจสองสามประการ โดยเฉพาะอย่างยิ่งเมื่อพูดถึงจำนวนเต็ม ประการหนึ่งวัตถุใด ๆ ที่แสดงเป็นค่าทันทีจะเป็นวัตถุเดียวกันเสมอไม่ว่าจะกำหนดให้ตัวแปรกี่ตัวก็ตาม มีออบเจ็กต์ 100 เพียงชิ้นเดียวเท่านั้นที่เป็นเท็จและอื่น ๆ ธรรมชาติที่ไม่ซ้ำกันในทันทีของตัวแปรที่ผูกมัดจำนวนเต็มอยู่เบื้องหลังการขาดตัวดำเนินการก่อนและหลังการเพิ่มของรูบี้กล่าวคือคุณไม่สามารถทำได้ใน Ruby: x = 1 x ++ # ไม่มีตัวดำเนินการดังกล่าวเหตุผลก็คือเนื่องจาก เมื่อมีการแสดง 1 ใน x ทันที x ++ จะเหมือนกับ 1 ++ ซึ่งหมายความว่าคุณกำลังเปลี่ยนเลข 1 เป็นเลข 2 - และนั่นก็ไม่สมเหตุสมผล ไม่ว่าจะกำหนดให้ตัวแปรกี่ตัวก็ตาม มีออบเจ็กต์ 100 เพียงชิ้นเดียวเท่านั้นที่เป็นเท็จและอื่น ๆ ธรรมชาติที่ไม่ซ้ำกันในทันทีของตัวแปรที่ผูกมัดจำนวนเต็มอยู่เบื้องหลังการขาดตัวดำเนินการก่อนและหลังการเพิ่มของรูบี้กล่าวคือคุณไม่สามารถทำได้ใน Ruby: x = 1 x ++ # ไม่มีตัวดำเนินการดังกล่าวเหตุผลก็คือเนื่องจาก เมื่อมีการแสดง 1 ใน x ทันที x ++ จะเหมือนกับ 1 ++ ซึ่งหมายความว่าคุณกำลังเปลี่ยนเลข 1 เป็นเลข 2 - และนั่นก็ไม่สมเหตุสมผล ไม่ว่าจะกำหนดให้ตัวแปรกี่ตัวก็ตาม มีออบเจ็กต์ 100 เพียงชิ้นเดียวเท่านั้นที่เป็นเท็จและอื่น ๆ ธรรมชาติที่ไม่ซ้ำกันในทันทีของตัวแปรที่ผูกมัดจำนวนเต็มอยู่เบื้องหลังการขาดตัวดำเนินการก่อนและหลังการเพิ่มของรูบี้กล่าวคือคุณไม่สามารถทำได้ใน Ruby: x = 1 x ++ # ไม่มีตัวดำเนินการดังกล่าวเหตุผลก็คือเนื่องจาก เมื่อมีการแสดง 1 ใน x ทันที x ++ จะเหมือนกับ 1 ++ ซึ่งหมายความว่าคุณกำลังเปลี่ยนเลข 1 เป็นเลข 2 - และนั่นก็ไม่สมเหตุสมผล


แต่ทำไมคุณถึงทำ "1.next" ได้ล่ะ?
Magne

1

ไม่สามารถทำได้โดยการเพิ่มวิธีการใหม่ในคลาส fixnum หรือจำนวนเต็ม?

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

ผลตอบแทน 2

ดูเหมือนว่าเมธอด "ทำลายล้าง" จะถูกต่อท้าย!เพื่อเตือนผู้ใช้ที่เป็นไปได้ดังนั้นการเพิ่มวิธีการใหม่ที่เรียกว่าnext!จึงค่อนข้างจะทำตามที่ร้องขอนั่นคือ

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

ผลตอบแทน 2 (เนื่องจากมีการเพิ่มความมึน)

แน่นอนว่าnext!วิธีนี้จะต้องตรวจสอบว่าวัตถุนั้นเป็นตัวแปรจำนวนเต็มไม่ใช่จำนวนจริง แต่ควรมีให้ใช้


1
Integer#nextมีอยู่แล้ว (มากหรือน้อย) ยกเว้นจะถูกเรียกInteger#succแทน (สำหรับ 'ผู้สืบทอด') แต่Integer#next!(หรือInteger#succ!) จะเป็นเรื่องไร้สาระ: จำได้ว่าวิธีการที่ทำงานเกี่ยวกับวัตถุที่ไม่ตัวแปรดังนั้นnumb.next!จะเท่ากับ1.next!ซึ่งก็คือการบอกว่ามันจะกลายพันธุ์ 1 จะเท่ากับ 2 ++จะดีกว่าเล็กน้อยขณะที่มันอาจจะเป็นน้ำตาลประโยคสำหรับการกำหนด =แต่ส่วนตัวผมชอบรูปแบบปัจจุบันที่ได้รับมอบหมายทั้งหมดจะทำด้วย
ปรัชญา

ในการกรอกความคิดเห็นด้านบน: และInteger#predเพื่อดึงข้อมูลรุ่นก่อน
โยนี

-6

ตรวจสอบตัวดำเนินการเหล่านี้จากตระกูล C ใน irb ของ Ruby และทดสอบด้วยตัวคุณเอง:

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

3
สิ่งนี้ผิดอย่างชัดเจนและไม่ได้ผลเช่นเดียวกับ(x++)คำสั่งที่ไม่ถูกต้องใน Ruby
anothermh
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.