Dlaczego Ruby nie obsługuje i ++ lub i-- (operatory inkrementacji / dekrementacji)?

130

Operator inkrementacji / dekrementacji pre / post ( ++i --) jest dość standardową składnią języka programowania (przynajmniej dla języków proceduralnych i obiektowych).

Dlaczego Ruby ich nie obsługuje? Rozumiem, że możesz osiągnąć to samo za pomocą +=i -=, ale wykluczenie czegoś takiego wydaje się dziwnie arbitralne, zwłaszcza że jest tak zwięzłe i konwencjonalne.

Przykład:

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

Rozumiem, że Fixnumjest niezmienny, ale jeśli +=mogę po prostu zainicjować nowy Fixnumi ustawić go, dlaczego nie zrobić tego samego dla ++?

Czy konsekwencja w przypisaniach zawierających =znak jest jedynym tego powodem, czy czegoś mi brakuje?

Andy_Vulhop
źródło
2
Kod źródłowy Grep Ruby dla takich operatorów. Jeśli ich nie ma - Matz ich nie lubi.
Eimantas,
Nie możesz zrobić preinkrementacji z +=operatorem. W CI spróbuj używać ++/ --tylko wewnątrz warunków warunkowych, preferując bardziej dosłowne +=/ -=w podstawowej instrukcji. Prawdopodobnie dlatego, że nauczyłem się Pythona (choć długo po C ...)
Nick T
Czy nie było wczoraj takiego pytania dla Pythona?
BoltClock
@Eimantas oczywiście twórcy języka ich nie lubili. Jest to zbyt powszechne, aby przeoczyć. Zastanawiałem się DLACZEGO, co zostało nieco wyjaśnione poniższymi odpowiedziami.
Andy_Vulhop,
1
Myślę, że jest to (prawie) modelowe pytanie SO. Uzyskanie przemyślanej odpowiedzi nie jest łatwe do wyszukania w Google. Jest dość jasne i konkretne, jaka odpowiedź jest wymagana, a odpowiedź rzuca światło na aspekt programowania, który może skłonić do myślenia szerszego niż tylko sedno pytania.
PurplePilot

Odpowiedzi:

97

Oto jak Matz (Yukihiro Matsumoto) wyjaśnia to w starym wątku :

Hi,

In message "[ruby-talk:02706] X++?"
    on 00/05/10, Aleksi Niemelä <[email protected]> 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.
Brian
źródło
10
2 i 3 wydają się sprzeczne. Jeśli przypisanie do siebie jest złe, dlaczego +=/ -=ok? I nie 1+=1byłoby tak źle? (Nie udaje się w IRB z syntax error, unexpected ASSIGNMENT)
Andy_Vulhop
2
(2) oznacza, że ​​w C nie zmieniasz samej wartości ... zmieniasz zawartość zmiennej, która przechowuje wartość. To trochę zbyt meta dla każdego języka, który przekazuje wartości. O ile nie ma sposobu na przekazanie czegoś przez odwołanie w Rubim (a mam na myśli naprawdę „przez referencję”, a nie przekazywanie referencji przez wartość), zmiana samej zmiennej nie byłaby możliwa w ramach metody.
cHao
5
Może czegoś mi brakuje. +=zastępuje obiekt, do którego odwołuje się zmienna, zupełnie nowym obiektem. Możesz to sprawdzić dzwoniąc i.object_idprzed i po i+=1. Dlaczego miałoby to być bardziej skomplikowane technicznie ++?
Andy_Vulhop
6
@Andy_Vulhop: # 3 wyjaśnia, dlaczego jest technicznie niemożliwe, aby przypisanie było metodą, a nie dlaczego przypisanie jest w ogóle niemożliwe (na plakacie, na który odpowiadał Matz, sądził, że możliwe jest stworzenie ++metody).
Chuck,
2
W Rubim wszystkie literały są również obiektami. Myślę więc, że Matz próbuje powiedzieć, że nie jest pewien, czy podoba mu się pomysł traktowania 1 ++ jako stwierdzenia. Osobiście uważam, że jest to nierozsądne, ponieważ @Andy_Vulhop mówi, że 1 + = 2 jest tak samo szalone, a Ruby po prostu zgłasza błąd, kiedy to robisz. Tak więc 1 ++ nie jest trudniejsze w obsłudze. Prawdopodobnie potrzeba poradzenia sobie przez parser z tego rodzaju cukrem syntaktycznym jest niepożądana.
Steve Midgley
28

Jednym z powodów jest to, że do tej pory każdy operator przypisania (tj. Operator zmieniający zmienną) ma w sobie znak =. Jeśli dodasz ++i --, to już nie jest.

Innym powodem jest to, że zachowanie ++i --często dezorientują ludzi. Przykład: wartość zwracana i++w Twoim przykładzie będzie w rzeczywistości wynosić 1, a nie 2 (nowa wartość ibędzie jednak wynosić 2).

sepp2k
źródło
4
Bardziej niż jakikolwiek inny powód do tej pory racjonalne stwierdzenie, że „wszystkie zadania mają =w sobie swoje”, wydaje się mieć sens. Mogę to trochę szanować jako zaciekłe trzymanie się konsekwencji.
Andy_Vulhop,
co z tym: a. kapitalizować! (niejawne przypisanie a)
Luís Soares
1
@ LuísSoares a.capitalize!nie zmienia przypisania a, zmieni ciąg, ado którego się odnosi. Wpłynie to na inne odwołania do tego samego ciągu, a jeśli zrobisz to a.object_idprzed i po wywołaniu funkcji capitalize, otrzymasz ten sam wynik (z których żaden nie byłby prawdziwy, gdybyś to zrobił a = a.capitalize).
sepp2k
1
@ LuísSoares Jak powiedziałem, a.capitalize!wpłynie to na inne odwołania do tego samego ciągu. To bardzo praktyczna różnica. Na przykład, jeśli masz, def yell_at(name) name.capitalize!; puts "HEY, #{name}!" enda następnie nazwiesz to w ten my_name = "luis"; yell_at(my_name)sposób:, wartość my_namewill będzie teraz wynosić "LUIS", podczas gdy nie zmieni się to, jeśli użyłeś capitalizei przypisanie.
wrzesień
1
Łał. To przerażające ... Świadomość, że w Javie ciągi znaków są niezmienne ... Ale z mocą wiąże się odpowiedzialność. Dziękuję za wyjaśnienie.
Luís Soares,
25

To nie jest konwencjonalne w językach OO. W rzeczywistości nie ma ++w Smalltalk, języku, który ukuł termin „programowanie obiektowe” (a język Ruby jest pod największym wpływem). Masz na myśli to, że jest konwencjonalny w C, a języki ściśle imitują C. Ruby ma składnię podobną do C, ale nie jest niewolniczy w trzymaniu się tradycji C.

Co do tego, dlaczego nie ma go w Ruby: Matz tego nie chciał. To naprawdę ostateczny powód.

Powodem ma czegoś takiego istnieje w Smalltalk to dlatego, że jest częścią języka jest nadrzędne filozofię, że przypisanie zmiennej jest zasadniczo inny rodzaj rzeczy niż wysyłanie wiadomości do obiektu - jest na innym poziomie. To myślenie prawdopodobnie wpłynęło na Matza podczas projektowania Rubiego.

Nie byłoby niemożliwe włączenie go do Rubiego - można łatwo napisać preprocesor, który przekształci wszystko ++w +=1. ale najwyraźniej Matzowi nie podobał się pomysł operatora, który wykonywał „ukryte zadanie”. Wydaje się również trochę dziwne, aby mieć operator z ukrytym operandem całkowitym w środku. Żaden inny operator w tym języku nie działa w ten sposób.

Gdakanie
źródło
1
Nie sądzę, że sugestia preprocesora zadziała; (nie jest ekspertem), ale myślę, że i = 42, i ++ zwróci 42, gdzie i + = 1 zwróci 43. Czy się mylę? Więc twoja sugestia w tym przypadku byłaby taka, aby użyć i ++, ponieważ ++ i jest zwykle używane, co jest dość złe imho i może spowodować więcej szkody niż pożytku.
AturSams
12

Myślę, że jest jeszcze jeden powód: ++w Rubim nie byłoby to nawet przydatne, jak w C i jego bezpośrednich następcach.

Powód jest taki, że forsłowo kluczowe: chociaż jest niezbędne w C, w Rubim jest przeważnie zbyteczne. Większość iteracji w Rubim odbywa się za pomocą metod Enumerable, takich jak eachi mappodczas iteracji przez pewną strukturę danych oraz Fixnum#timesmetody, gdy trzeba wykonać pętlę dokładną liczbę razy.

Właściwie, o ile widziałem, większość czasu +=1jest używana przez osoby, które niedawno przeszły na Ruby z języków w stylu C.

Krótko mówiąc, naprawdę wątpliwe jest, czy metody ++i --czy w ogóle byłyby używane.

Mladen Jablanović
źródło
1
To najlepsza odpowiedź imho. ++ jest często używany do iteracji. Ruby nie zachęca do tego typu iteracji.
AturSams
3

Myślę, że uzasadnienie Matza, dlaczego ich nie lubi, jest takie, że faktycznie zastępuje zmienną nową.

dawny:

a = SomeClass.new
def a.go
  'Witaj'
koniec
# w tym momencie możesz zadzwonić do a.go
# ale jeśli zrobiłeś a ++
# to naprawdę oznacza a = a + 1
# więc nie możesz już dzwonić do a.go
# ponieważ zgubiłeś oryginał

Teraz gdyby ktoś mógł go przekonać, że powinien po prostu zadzwonić na #succ! a co nie, to miałoby większy sens i pozwoliło uniknąć problemu. Możesz zasugerować to na rubinowym rdzeniu.

rogerdpack
źródło
9
„Możesz zasugerować to na rdzeniu rubinowym” ... Po przeczytaniu i zrozumieniu argumentów we wszystkich innych wątkach, w których było to sugerowane ostatnim razem, a także wcześniej i wcześniej i wcześniej, i wcześniej, i ... Nie byłem w społeczności Ruby bardzo długo, ale w swoim czasie pamiętam co najmniej dwadzieścia takich dyskusji.
Jörg W Mittag,
3

Możesz zdefiniować .+operator autoinkrementacji:

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

Więcej informacji na temat „zmiennej klasy” można znaleźć w sekcjiZmienna klasy w celu zwiększenia liczby obiektów o stałej liczbie ”.

Sony Santos
źródło
2

I słowami Davida Blacka z jego książki „The Well-Grounded Rubyist”:

Niektóre obiekty w Rubim są przechowywane w zmiennych jako wartości bezpośrednie. Należą do nich liczby całkowite, symbole (które wyglądają tak: this) i specjalne obiekty true, false i nil. Po przypisaniu jednej z tych wartości do zmiennej (x = 1), zmienna przechowuje samą wartość, a nie odniesienie do niej. W praktyce nie ma to znaczenia (i często będzie to sugerowane, a nie powtarzane wielokrotnie, w dyskusjach na temat odniesień i powiązanych tematów w tej książce). Ruby automatycznie obsługuje wyłuskiwanie odniesień do obiektów; nie musisz wykonywać żadnej dodatkowej pracy, aby wysłać wiadomość do obiektu, który zawiera, powiedzmy, odniesienie do łańcucha, w przeciwieństwie do obiektu, który zawiera bezpośrednią wartość całkowitą. Jednak zasada reprezentacji wartości bezpośredniej ma kilka interesujących konsekwencji, zwłaszcza jeśli chodzi o liczby całkowite. Po pierwsze, każdy obiekt, który jest reprezentowany jako wartość natychmiastowa, jest zawsze dokładnie tym samym obiektem, bez względu na to, do ilu zmiennych jest przypisany. Jest tylko jeden obiekt 100, tylko jeden obiekt fałszywy i tak dalej. Bezpośrednia, unikalna natura zmiennych związanych z liczbami całkowitymi leży u podstaw braku operatorów przed i po inkrementacji w Rubim - co oznacza, że ​​nie można tego zrobić w Rubim: x = 1 x ++ # Brak takiego operatora Przyczyna jest taka do bezpośredniej obecności 1 w x, x ++ byłoby podobne do 1 ++, co oznacza, że ​​zmieniłbyś liczbę 1 na liczbę 2 - i to nie ma sensu. bez względu na to, do ilu zmiennych jest przypisana. Jest tylko jeden obiekt 100, tylko jeden obiekt fałszywy i tak dalej. Bezpośrednia, unikalna natura zmiennych związanych z liczbami całkowitymi leży u podstaw braku operatorów przed i po inkrementacji w Rubim - co oznacza, że ​​nie można tego zrobić w Rubim: x = 1 x ++ # Brak takiego operatora Powód jest taki do bezpośredniej obecności 1 w x, x ++ byłoby podobne do 1 ++, co oznacza, że ​​zmieniłbyś liczbę 1 na liczbę 2 - i to nie ma sensu. bez względu na to, do ilu zmiennych jest przypisana. Jest tylko jeden obiekt 100, tylko jeden obiekt fałszywy i tak dalej. Bezpośrednia, unikalna natura zmiennych związanych z liczbami całkowitymi leży u podstaw braku operatorów przed i po inkrementacji w Rubim - co oznacza, że ​​nie można tego zrobić w Rubim: x = 1 x ++ # Brak takiego operatora Przyczyna jest taka do bezpośredniej obecności 1 w x, x ++ byłoby podobne do 1 ++, co oznacza, że ​​zmieniłbyś liczbę 1 na liczbę 2 - i to nie ma sensu.

Alexander Swann
źródło
Ale dlaczego w takim razie możesz zrobić „1. następny”?
Magne
1

Czy nie można tego osiągnąć, dodając nową metodę do klasy fixnum lub Integer?

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

zwraca 2

Wydaje się, że metody „destrukcyjne” są dołączane w !celu ostrzeżenia potencjalnych użytkowników, więc dodanie nowej metody o nazwie w next!zasadzie zrobiłoby to, o co proszono, tj.

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

zwraca 2 (ponieważ liczba numb została zwiększona)

Oczywiście next!metoda musiałaby sprawdzić, czy obiekt był zmienną całkowitą, a nie liczbą rzeczywistą, ale ta powinna być dostępna.

Sjerek
źródło
1
Integer#nextjuż istnieje (mniej więcej), z wyjątkiem tego, że jest nazywany Integer#succ(od „następcy”). Ale Integer#next!(lub Integer#succ!) byłoby nonsensem: pamiętaj, że metody działają na obiektach , a nie na zmiennych , więc numb.next!byłyby dokładnie równe 1.next!, co oznacza, że mutacja 1 byłaby równa 2 . ++byłoby nieznacznie lepsze, ponieważ mogłoby to być cukier składniowy dla przydziału, ale osobiście wolę obecną składnię, w której wszystkie przypisania są wykonywane =.
filomoria
Aby uzupełnić powyższy komentarz: i Integer#predpobrać poprzednika.
Yoni
-6

Sprawdź te operatory z rodziny C w irb Rubiego i przetestuj je samodzielnie:

x = 2    # x is 2
x += 2   # x is 4
x++      # x is now 8
++x      # x reverse to 4
Aung Zan Baw
źródło
3
Jest to ewidentnie błędne i nie działa, podobnie jak (x++)nieprawidłowe stwierdzenie w Rubim.
anothermh