¿Por qué Ruby no admite i ++ o i-- (operadores de incremento / decremento)?

130

El operador de incremento / decremento previo / posterior ( ++y --) son una sintaxis de lenguaje de programación bastante estándar (al menos para lenguajes de procedimiento y orientados a objetos).

¿Por qué Ruby no los apoya? Entiendo que podrías lograr lo mismo con +=y -=, pero parece extrañamente arbitrario excluir algo así, especialmente porque es tan conciso y convencional.

Ejemplo:

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

Entiendo que Fixnumes inmutable, pero si +=solo puedo instanciar un nuevo Fixnumy configurarlo, ¿por qué no hacer lo mismo ++?

¿Es la coherencia en las tareas que contienen el =carácter la única razón para esto, o me falta algo?

Andy_Vulhop
fuente
2
Código fuente de grep ruby ​​para dichos operadores. Si no hay ninguno, a Matz no le gustan.
Eimantas
No puede hacer preincrement con un +=operador. En CI intente usar ++/ --solo dentro de condicionales, prefiriendo los más literales +=/ -=en una declaración básica. Probablemente porque aprendí Python (mucho después de C ...)
Nick T
¿No hubo una pregunta como esta para Python ayer?
BoltClock
@Eimantas, obviamente, a los creadores del lenguaje no les gustaron. Es muy común pasarlo por alto. Me preguntaba POR QUÉ, que de alguna manera ha sido aclarado por las respuestas a continuación.
Andy_Vulhop
1
Creo que esta es (casi) una pregunta modelo SO. No es algo que no se pueda buscar fácilmente en Google para obtener una respuesta considerada. Es bastante claro y específico en qué respuesta se requiere y la respuesta arroja luz sobre una faceta de la programación que puede hacer que uno piense más ampliamente que solo el núcleo de la pregunta.
PurplePilot

Respuestas:

97

Así es como Matz (Yukihiro Matsumoto) lo explica en un viejo hilo :

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
fuente
10
2 y 3 parecen contradictorios. Si la autoasignación es mala, ¿por qué +=/ -=ok? ¿Y no 1+=1sería tan malo? (Falla en IRB con syntax error, unexpected ASSIGNMENT)
Andy_Vulhop
2
(2) significa que en C, no está alterando el valor en sí ... está alterando el contenido de la variable que contiene el valor. Eso es demasiado meta para cualquier lenguaje que pase por valor. A menos que haya una manera de pasar algo por referencia en Ruby (y quiero decir realmente "por referencia", no pasando una referencia por valor), no sería posible alterar la variable en sí misma dentro de un método.
cHao
55
Tal vez me estoy perdiendo algo aquí. +=reemplaza el objeto al que hace referencia la variable con un objeto completamente nuevo. Puede verificar esto llamando i.object_idantes y después i+=1. ¿Por qué sería eso técnicamente más difícil de hacer ++?
Andy_Vulhop
66
@Andy_Vulhop: # 3 está explicando por qué es técnicamente imposible que la asignación sea un método, no por qué la asignación es imposible en general (el póster al que Matz respondía pensó que podría ser posible crear un ++método).
Chuck
2
En Ruby, todos los literales son también objetos. Así que creo que Matz está tratando de decir que no está seguro de que le guste la idea de tratar con 1 ++ como una declaración. Personalmente, creo que esto no es razonable, ya que @Andy_Vulhop dice que 1 + = 2 es igual de loco, y Ruby solo genera un error cuando haces esto. Entonces 1 ++ no es más difícil de manejar. Posiblemente, la necesidad del analizador de hacer frente a ese tipo de azúcar sintáctica no sea deseable.
Steve Midgley
28

Una razón es que, hasta ahora, cada operador de asignación (es decir, un operador que cambia una variable) tiene un contenido =. Si agrega ++y --, ese ya no es el caso.

Otra razón es que el comportamiento ++y a --menudo confunde a las personas. Caso en cuestión: el valor de retorno de i++en su ejemplo en realidad sería 1, no 2 (el nuevo valor de isería 2, sin embargo).

sepp2k
fuente
44
Más que cualquier otra razón hasta el momento, =parece racional tener la razón de que "todas las tareas tienen algo en ellas". Puedo respetar eso como una feroz adhesión a la consistencia.
Andy_Vulhop
¿Qué pasa con esto: a.capitalize! (asignación implícita de a)
Luís Soares
1
@ LuísSoares a.capitalize!no se reasigna a, mutará la cadena a la que se arefiere. Otras referencias a la misma cadena se verán afectadas y si lo hace a.object_idantes y después de la llamada a capitalize, obtendrá el mismo resultado (ninguno de los cuales sería cierto si lo hiciera en su a = a.capitalizelugar).
sepp2k
1
@ LuísSoares Como dije, a.capitalize!afectará otras referencias a la misma cadena. Esa es una gran diferencia práctica. Por ejemplo, si tiene def yell_at(name) name.capitalize!; puts "HEY, #{name}!" endy luego lo llama así: my_name = "luis"; yell_at(my_name)el valor de my_nameahora será "LUIS", mientras que no se vería afectado si hubiera utilizado capitalizeuna tarea.
sepp2k
1
Guau. Eso da miedo ... Saber que en Java las cadenas son inmutables ... Pero con el poder viene la responsabilidad. Gracias por la explicación.
Luís Soares
25

No es convencional en los idiomas OO. De hecho, no existe ++en Smalltalk, el lenguaje que acuñó el término "programación orientada a objetos" (y el lenguaje en el que Ruby está más fuertemente influenciado). Lo que quiere decir es que es convencional en C y lenguajes que imitan estrechamente a C. Ruby tiene una sintaxis algo similar a C, pero no es servil al adherirse a las tradiciones de C.

En cuanto a por qué no está en Ruby: Matz no lo quería. Esa es realmente la razón final.

La razón por la que no existe tal cosa en Smalltalk es porque es parte de la filosofía primordial del lenguaje que asignar una variable es fundamentalmente un tipo diferente de cosa que enviar un mensaje a un objeto: está en un nivel diferente. Este pensamiento probablemente influyó en Matz en el diseño de Ruby.

No sería imposible incluirlo en Rubí - fácilmente se podría escribir un preprocesador que transforma todo ++en +=1. pero evidentemente a Matz no le gustó la idea de un operador que hiciera una "tarea oculta". También parece un poco extraño tener un operador con un operando entero oculto dentro de él. Ningún otro operador en el idioma funciona de esa manera.

Arrojar
fuente
1
No creo que su sugerencia de preprocesador funcione; (no es un experto) pero creo que i = 42, i ++ devolverá 42 donde i + = 1 devolvería 43. ¿Soy incorrecto en esto? Entonces, su sugerencia en ese caso sería usar i ++ como ++ i normalmente se usa, lo cual es bastante malo y puede causar más daño que bien.
AturSams
12

Creo que hay otra razón: ++en Ruby no sería remotamente útil como en C y sus sucesores directos.

La razón es la forpalabra clave: si bien es esencial en C, en su mayoría es superflua en Ruby. La mayor parte de la iteración en Ruby se realiza a través de métodos Enumerables, como eachy mapcuando se itera a través de alguna estructura de datos, yFixnum#times método, cuando necesita recorrer un número exacto de veces.

En realidad, por lo que he visto, la mayoría del tiempo +=1es utilizado por personas recién migradas a Ruby desde lenguajes de estilo C.

En resumen, es realmente cuestionable si los métodos ++y --se utilizarían en absoluto.

Mladen Jablanović
fuente
1
Esta es la mejor respuesta en mi humilde opinión. ++ a menudo se usa para la iteración. Ruby no fomenta este tipo de iteración.
AturSams
3

Creo que el razonamiento de Matz para no gustarles es que en realidad reemplaza la variable con una nueva.

ex:

a = SomeClass.new
def a.go
  'Hola'
final
# en este punto, puede llamar a a.go
# pero si hiciste un a ++
# que realmente significa a = a + 1
# para que ya no puedas llamar a a.go
# como has perdido tu original

¡Ahora si alguien pudiera convencerlo de que debería llamar a #succ! o lo que no, eso tendría más sentido y evitaría el problema. Puedes sugerirlo en ruby ​​core.

rogerdpack
fuente
9
"Puede sugerirlo en Ruby Core" ... Después de haber leído y entendido los argumentos en todos los otros hilos donde se sugirió la última vez, y el tiempo anterior y el anterior y el anterior, y el tiempo anterior a eso, y ... No he estado en la comunidad de Ruby por mucho tiempo, pero solo durante mi tiempo, recuerdo al menos veinte de esas discusiones.
Jörg W Mittag
3

Puede definir un .+operador de autoincremento:

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

Más información sobre "variable de clase" está disponible en " Variable de clase para incrementar los objetos Fixnum ".

Sony Santos
fuente
2

Y en palabras de David Black de su libro "El rubí bien cimentado":

Algunos objetos en Ruby se almacenan en variables como valores inmediatos. Estos incluyen enteros, símbolos (que se parecen a esto) y los objetos especiales verdadero, falso y nulo. Cuando asigna uno de estos valores a una variable (x = 1), la variable contiene el valor en sí mismo, en lugar de una referencia a él. En términos prácticos, esto no importa (y a menudo se dejará como implícito, en lugar de explicarse repetidamente, en las discusiones de referencias y temas relacionados en este libro). Ruby maneja la desreferenciación de referencias de objeto automáticamente; no tiene que hacer ningún trabajo adicional para enviar un mensaje a un objeto que contiene, por ejemplo, una referencia a una cadena, en lugar de un objeto que contiene un valor entero inmediato. Pero la regla de representación de valor inmediato tiene un par de ramificaciones interesantes, especialmente cuando se trata de enteros. Por un lado, cualquier objeto que se represente como un valor inmediato siempre es exactamente el mismo objeto, sin importar a cuántas variables esté asignado. Solo hay un objeto 100, solo un objeto falso, y así sucesivamente. La naturaleza única e inmediata de las variables unidas a enteros está detrás de la falta de operadores de pre y post incremento de Ruby, es decir, no se puede hacer esto en Ruby: x = 1 x ++ # No existe dicho operador. a la presencia inmediata de 1 en x, x ++ sería como 1 ++, lo que significa que estaría cambiando el número 1 al número 2, y eso no tiene sentido. no importa a cuántas variables se le asigne. Solo hay un objeto 100, solo un objeto falso, y así sucesivamente. La naturaleza única e inmediata de las variables unidas a enteros está detrás de la falta de operadores de pre y post incremento de Ruby, es decir, no se puede hacer esto en Ruby: x = 1 x ++ # No existe dicho operador. a la presencia inmediata de 1 en x, x ++ sería como 1 ++, lo que significa que estaría cambiando el número 1 al número 2, y eso no tiene sentido. no importa a cuántas variables se le asigne. Solo hay un objeto 100, solo un objeto falso, y así sucesivamente. La naturaleza única e inmediata de las variables unidas a enteros está detrás de la falta de operadores de pre y post incremento de Ruby, es decir, no se puede hacer esto en Ruby: x = 1 x ++ # No existe dicho operador. a la presencia inmediata de 1 en x, x ++ sería como 1 ++, lo que significa que estaría cambiando el número 1 al número 2, y eso no tiene sentido.

Alexander Swann
fuente
¿Pero cómo puedes hacer "1.siguiente" entonces?
Magne
1

¿No podría lograrse esto agregando un nuevo método a la clase Fixnum o Integer?

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

devuelve 2

Parece que se agregan métodos "destructivos" !para advertir a los posibles usuarios, por lo que agregar un nuevo método llamado next!prácticamente haría lo que se solicita, es decir.

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

devuelve 2 (ya que el adormecimiento se ha incrementado)

Por supuesto, el next!método tendría que verificar que el objeto fuera una variable entera y no un número real, pero esto debería estar disponible.

Sjerek
fuente
1
Integer#nextya existe (más o menos), excepto que se llama en su Integer#succlugar (para 'sucesor'). Pero Integer#next!(o Integer#succ!) sería una tontería: recuerde que los métodos funcionan en objetos , no en variables , por numb.next!lo que serían exactamente iguales 1.next!, es decir, mutarían 1 para ser igual a 2 . ++sería marginalmente mejor ya que podría ser azúcar sintáctica para una tarea, pero personalmente prefiero la sintaxis actual donde se realizan todas las tareas =.
philomory
Para completar el comentario anterior: y Integer#predpara recuperar el predecesor.
Yoni
-6

Verifique estos operadores de la familia C en el irb de Ruby y pruébelos usted mismo:

x = 2    # x is 2
x += 2   # x is 4
x++      # x is now 8
++x      # x reverse to 4
Aung Zan Baw
fuente
3
Esto es claramente incorrecto y no funciona, ya que (x++)es una declaración no válida en Ruby.
anothermh