Pourquoi Ruby ne prend-il pas en charge i ++ ou i-- (opérateurs d'incrémentation / décrémentation)?

130

L'opérateur d'incrémentation / décrémentation pré / post ( ++et --) est une syntaxe de langage de programmation assez standard (au moins pour les langages procéduraux et orientés objet).

Pourquoi Ruby ne les prend-il pas en charge? Je comprends que vous pourriez accomplir la même chose avec +=et -=, mais il semble tout simplement bizarrement arbitraire d'exclure quelque chose comme ça, d'autant plus que c'est tellement concis et conventionnel.

Exemple:

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

Je comprends que Fixnumc'est immuable, mais si vous +=pouvez simplement instancier un nouveau Fixnumet le définir, pourquoi ne pas faire de même pour ++?

La cohérence des affectations contenant le =personnage est-elle la seule raison à cela, ou est-ce que je manque quelque chose?

Andy_Vulhop
la source
2
Code source de Grep ruby ​​pour ces opérateurs. S'il n'y en a pas, Matz ne les aime pas.
Eimantas
Vous ne pouvez pas faire de préincrément avec un +=opérateur. Dans CI, essayez d'utiliser ++/ --uniquement à l'intérieur des conditions, préférant le plus littéral +=/ -=dans une instruction de base. Probablement parce que j'ai appris Python (bien après C cependant ...)
Nick T
N'y avait-il pas une question comme celle-ci pour Python hier?
BoltClock
@Eimantas bien évidemment le (s) créateur (s) de la langue ne les aimait pas. C'est trop courant pour être négligé. Je me demandais POURQUOI, ce qui a été quelque peu clarifié par les réponses ci-dessous.
Andy_Vulhop
1
Je pense que c'est (presque) une question modèle SO. Ce n'est pas quelque chose qui n'est pas facilement googleable pour obtenir une réponse réfléchie. La réponse requise est assez claire et précise et la réponse met en lumière une facette de la programmation qui peut faire réfléchir plus largement que le cœur de la question.
PurplePilot

Réponses:

97

Voici comment Matz (Yukihiro Matsumoto) l'explique dans un vieux fil :

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
la source
10
2 et 3 semblent contradictoires. Si l'auto-attribution est mauvaise, pourquoi est-ce +=/ -=ok? Et ne serait pas 1+=1aussi mauvais? (Il échoue dans IRB avec syntax error, unexpected ASSIGNMENT)
Andy_Vulhop
2
(2) signifie qu'en C, vous ne modifiez pas la valeur elle-même ... vous modifiez le contenu de la variable qui contient la valeur. C'est un peu trop méta pour tout langage qui passe par valeur. À moins qu'il n'y ait un moyen de passer quelque chose par référence dans Ruby (et je veux dire vraiment "par référence", sans passer une référence par valeur), modifier la variable elle-même ne serait pas possible dans une méthode.
cHao
5
Il me manque peut-être quelque chose ici. +=remplace l'objet auquel la variable fait référence par un tout nouvel objet. Vous pouvez vérifier cela en appelant i.object_idavant et après i+=1. Pourquoi est-ce que cela serait techniquement plus difficile à faire ++?
Andy_Vulhop
6
@Andy_Vulhop: # 3 explique pourquoi il est techniquement impossible que l'affectation soit une méthode, pas pourquoi l'affectation est impossible en général (l'affiche Matz répondait à l'idée qu'il serait possible de créer une ++méthode).
Chuck
2
Dans Ruby, tous les littéraux sont également des objets. Je crois donc que Matz essaie de dire qu'il n'est pas sûr qu'il aime l'idée de traiter 1 ++ comme une déclaration. Personnellement, je pense que c'est déraisonnable car, comme le dit @Andy_Vulhop, 1 + = 2 est tout aussi farfelu, et Ruby soulève simplement une erreur lorsque vous faites cela. Donc 1 ++ n'est pas plus difficile à gérer. Le besoin de l'analyseur de faire face à ce type de sucre syntaxique n'est peut-être pas souhaitable.
Steve Midgley
28

Une des raisons est que jusqu'à présent, chaque opérateur d'affectation (c'est-à-dire un opérateur qui change une variable) a un =en lui. Si vous ajoutez ++et --, ce n'est plus le cas.

Une autre raison est que le comportement ++et --souvent confondent les gens. i++Exemple concret: la valeur de retour de dans votre exemple serait en fait 1, et non 2 (la nouvelle valeur de iserait cependant 2).

sepp2k
la source
4
Plus que toute autre raison jusqu'à présent, le rationnel selon lequel «toutes les affectations ont un =en eux» semble avoir un sens. Je peux en quelque sorte respecter cela comme une adhésion farouche à la cohérence.
Andy_Vulhop
qu'en est-il de ceci: a. capitaliser! (affectation implicite d'un)
Luís Soares
1
@ LuísSoares a.capitalize!ne réassigne pas a, il mute la chaîne qui afait référence. Les autres références à la même chaîne seront affectées et si vous le faites a.object_idavant et après l'appel à capitalize, vous obtiendrez le même résultat (qui ne serait pas vrai si vous le faisiez à la a = a.capitalizeplace).
sepp2k
1
@ LuísSoares Comme je l'ai dit, a.capitalize!affectera d'autres références à la même chaîne. C'est vraiment une différence pratique. Par exemple, si vous avez def yell_at(name) name.capitalize!; puts "HEY, #{name}!" endet que vous l'appelez comme ceci:, my_name = "luis"; yell_at(my_name)la valeur de my_namesera maintenant "LUIS", alors qu'elle ne serait pas affectée si vous aviez utilisé capitalizeet une affectation.
sepp2k
1
Sensationnel. C'est effrayant ... Sachant qu'en Java, les chaînes sont immuables. Mais avec le pouvoir vient la responsabilité. Merci pour l'explication.
Luís Soares
25

Ce n'est pas conventionnel dans les langages OO. En fait, il n'y ++en a pas dans Smalltalk, le langage qui a inventé le terme «programmation orientée objet» (et le langage par lequel Ruby est le plus fortement influencé). Ce que vous voulez dire, c'est qu'il est conventionnel en C et que les langages imitant de près C. Ruby a une syntaxe un peu semblable au C, mais ce n'est pas servile à adhérer aux traditions C.

Quant à savoir pourquoi ce n'est pas dans Ruby: Matz n'en voulait pas. C'est vraiment la raison ultime.

La raison pour laquelle une telle chose n'existe pas dans Smalltalk est que cela fait partie de la philosophie primordiale du langage selon laquelle l'attribution d'une variable est fondamentalement un autre type de chose que l'envoi d'un message à un objet - c'est à un niveau différent. Cette réflexion a probablement influencé Matz dans la conception de Ruby.

Il ne serait pas impossible de l'inclure dans Ruby - vous pourriez facilement écrire un préprocesseur qui transforme tout ++en +=1. mais évidemment, Matz n'aimait pas l'idée d'un opérateur qui faisait une "mission cachée". Il semble également un peu étrange d'avoir un opérateur avec un opérande entier caché à l'intérieur. Aucun autre opérateur dans la langue ne fonctionne de cette façon.

Mandrin
la source
1
Je ne pense pas que votre suggestion de préprocesseur fonctionnerait; (pas un expert) mais je pense que i = 42, i ++ renverra 42 où i + = 1 renverrait 43. Est-ce que je me trompe? Donc, votre suggestion dans ce cas serait d'utiliser i ++ car ++ i est normalement utilisé, ce qui est assez mauvais à mon avis et peut causer plus de mal que de bien.
AturSams
12

Je pense qu'il y a une autre raison: ++dans Ruby ne serait pas utile à distance comme dans C et ses successeurs directs.

La raison en est, le formot - clé: s'il est essentiel en C, il est surtout superflu en Ruby. La plupart de l'itération dans Ruby est effectuée via des méthodes Enumerable, telles que eachet maplors de l'itération dans une structure de données et une Fixnum#timesméthode, lorsque vous devez effectuer une boucle un nombre exact de fois.

En fait, pour autant que je l'ai vu, la plupart du temps +=1est utilisé par des personnes fraîchement migrées vers Ruby à partir de langages de style C.

En bref, on peut vraiment se demander si des méthodes ++et --seraient utilisées du tout.

Mladen Jablanović
la source
1
C'est la meilleure réponse à mon humble avis. ++ est souvent utilisé pour l'itération. Ruby n'encourage pas ce type d'itération.
AturSams
3

Je pense que le raisonnement de Matz pour ne pas les aimer est qu'il remplace en fait la variable par une nouvelle.

ex:

a = SomeClass.new
def a.go
  'Bonjour'
fin
# à ce stade, vous pouvez appeler a.go
# mais si vous avez fait un a ++
# cela signifie vraiment a = a + 1
# afin que vous ne puissiez plus appeler a.go
# car vous avez perdu votre original

Maintenant, si quelqu'un pouvait le convaincre qu'il devrait simplement appeler #succ! ou quoi pas, cela aurait plus de sens et éviterait le problème. Vous pouvez le suggérer sur un noyau rubis.

rogerdpack
la source
9
"Vous pouvez le suggérer sur le noyau ruby" ... Après avoir lu et compris les arguments dans tous les autres fils où il a été suggéré la dernière fois, et avant cela, et avant cela, et avant cela, et le temps avant ça, et ... je ne suis pas dans la communauté Ruby depuis très longtemps, mais juste pendant mon temps, je me souviens d'au moins vingt discussions de ce genre.
Jörg W Mittag
3

Vous pouvez définir un .+opérateur d'auto-incrémentation:

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

Plus d'informations sur la "variable de classe" sont disponibles dans " Variable de classe pour incrémenter les objets Fixnum ".

Sony Santos
la source
2

Et comme l'a dit David Black dans son livre "The Well-Grounded Rubyist":

Certains objets de Ruby sont stockés dans des variables en tant que valeurs immédiates. Ceux-ci incluent des entiers, des symboles (qui ressemblent à ceci) et les objets spéciaux true, false et nil. Lorsque vous affectez l'une de ces valeurs à une variable (x = 1), la variable contient la valeur elle-même, plutôt qu'une référence à celle-ci. En termes pratiques, cela n'a pas d'importance (et cela sera souvent laissé comme implicite, plutôt que répété, dans les discussions sur les références et les sujets connexes dans ce livre). Ruby gère automatiquement le déréférencement des références d'objets; vous n'avez pas à faire de travail supplémentaire pour envoyer un message à un objet qui contient, par exemple, une référence à une chaîne, par opposition à un objet qui contient une valeur entière immédiate. Mais la règle de représentation de la valeur immédiate a quelques ramifications intéressantes, surtout en ce qui concerne les nombres entiers. D'une part, tout objet représenté comme une valeur immédiate est toujours exactement le même objet, quel que soit le nombre de variables auxquelles il est affecté. Il n'y a qu'un seul objet 100, un seul objet faux, et ainsi de suite. La nature immédiate et unique des variables liées aux entiers est à l'origine du manque d'opérateurs pré et post-incrément de Ruby - c'est-à-dire que vous ne pouvez pas faire cela dans Ruby: x = 1 x ++ # Aucun opérateur de ce type La raison en est que à la présence immédiate de 1 dans x, x ++ serait comme 1 ++, ce qui signifie que vous changeriez le nombre 1 en nombre 2 - et cela n'a aucun sens. peu importe le nombre de variables auxquelles il est affecté. Il n'y a qu'un seul objet 100, un seul objet faux, et ainsi de suite. La nature immédiate et unique des variables liées aux entiers est à l'origine du manque d'opérateurs pré et post-incrément de Ruby - c'est-à-dire que vous ne pouvez pas faire cela dans Ruby: x = 1 x ++ # Aucun opérateur de ce type La raison en est que à la présence immédiate de 1 dans x, x ++ serait comme 1 ++, ce qui signifie que vous changeriez le nombre 1 en nombre 2 - et cela n'a aucun sens. peu importe le nombre de variables auxquelles il est affecté. Il n'y a qu'un seul objet 100, un seul objet faux, et ainsi de suite. La nature immédiate et unique des variables liées aux entiers est à l'origine du manque d'opérateurs pré et post-incrément de Ruby - c'est-à-dire que vous ne pouvez pas faire cela dans Ruby: x = 1 x ++ # Aucun opérateur de ce type La raison en est que à la présence immédiate de 1 dans x, x ++ serait comme 1 ++, ce qui signifie que vous changeriez le nombre 1 en nombre 2 - et cela n'a aucun sens.

Alexander Swann
la source
Mais comment se fait-il que vous puissiez faire "1.next" alors?
Magne
1

Cela ne pourrait-il pas être réalisé en ajoutant une nouvelle méthode à la classe fixnum ou Integer?

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

renvoie 2

Les méthodes "destructives" semblent être ajoutées !pour avertir les utilisateurs potentiels, donc l'ajout d'une nouvelle méthode appelée next!ferait à peu près ce qui a été demandé.

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

renvoie 2 (puisque numb a été incrémenté)

Bien sûr, la next!méthode devrait vérifier que l'objet était une variable entière et non un nombre réel, mais cela devrait être disponible.

Sjerek
la source
1
Integer#nextexiste déjà (plus ou moins), sauf qu'il est appelé à la Integer#succplace (pour 'successeur'). Mais Integer#next!(ou Integer#succ!) serait absurde: rappelez-vous que les méthodes fonctionnent sur des objets , pas sur des variables , donc numb.next!serait exactement égale à 1.next!, c'est-à-dire qu'elle muterait 1 pour être égal à 2 . ++serait légèrement mieux car cela pourrait être du sucre syntaxique pour une affectation, mais personnellement, je préfère la syntaxe actuelle où toutes les affectations sont terminées =.
philomory
Pour compléter le commentaire ci-dessus: et Integer#predpour récupérer le prédécesseur.
Yoni
-6

Vérifiez ces opérateurs de la famille C dans irb de Ruby et testez-les par vous-même:

x = 2    # x is 2
x += 2   # x is 4
x++      # x is now 8
++x      # x reverse to 4
Aung Zan Baw
la source
3
Ceci est clairement faux et ne fonctionne pas, tout comme (x++)une déclaration invalide dans Ruby.
anothermh