Por que o Ruby não suporta i ++ ou i-- (operadores de incremento / decremento)?

130

O operador pré / pós incremento / decremento ( ++e --) é uma sintaxe de linguagem de programação bastante padrão (pelo menos para linguagens procedurais e orientadas a objetos).

Por que Ruby não os suporta? Entendo que você poderia realizar a mesma coisa com +=e -=, mas parece estranhamente arbitrário excluir algo assim, especialmente porque é tão conciso e convencional.

Exemplo:

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

Entendo que Fixnumé imutável, mas se é +=possível instanciar um novo Fixnume configurá-lo, por que não fazer o mesmo ++?

A consistência nas tarefas que contêm o =personagem é a única razão para isso ou estou faltando alguma coisa?

Andy_Vulhop
fonte
2
Código-fonte Grep Ruby para esses operadores. Se não houver, Matz não gosta deles.
Eimantas
Você não pode fazer pré-incremento com um +=operador. No IC, tente usar ++/ --apenas dentro de condicionais, preferindo o mais literal +=/ -=em uma instrução básica. Provavelmente porque eu aprendi Python (muito tempo depois C embora ...)
Nick T
Não havia uma pergunta como esta para Python ontem?
BoltClock
@Eimantas bem, obviamente, os criadores da linguagem não gostaram deles. É muito comum ignorar. Fiquei me perguntando por que, que foi um pouco esclarecido pelas respostas abaixo.
Andy_Vulhop
1
Eu acho que isso é (quase) uma questão SO modelo. Não é algo fácil de obter para obter uma resposta considerada. É bastante claro e específico em que resposta é necessária e a resposta lança luz sobre uma faceta da programação que pode fazer alguém pensar de maneira mais ampla do que apenas o cerne da questão.
PurplePilot

Respostas:

97

Aqui está como Matz (Yukihiro Matsumoto) explica isso em um tópico antigo :

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
fonte
10
2 e 3 parecem contraditórios. Se a auto-atribuição é ruim, por que +=/ -=ok? E não 1+=1seria tão ruim assim? (Falha no IRB com syntax error, unexpected ASSIGNMENT)
Andy_Vulhop
2
(2) significa que em C, você não está alterando o próprio valor ... você está alterando o conteúdo da variável que contém o valor. Isso é um pouco demais para qualquer linguagem que passe por valor. A menos que haja uma maneira de passar algo por referência em Ruby (e eu quero dizer verdadeiramente "por referência", não passando uma referência por valor), alterar a variável em si não seria possível dentro de um método.
cHao
5
Talvez esteja faltando alguma coisa aqui. +=substitui o objeto que a variável faz referência por um objeto totalmente novo. Você pode verificar isso ligando i.object_idantes e depois i+=1. Por que isso seria tecnicamente mais complicado ++?
Andy_Vulhop
6
@Andy_Vulhop: # 3 está explicando por que é tecnicamente impossível a atribuição ser um método, não por que a atribuição é impossível em geral (o cartaz que Matz estava respondendo achou que seria possível criar um ++método).
Chuck
2
No Ruby, todos os literais também são objetos. Portanto, acredito que Matz está tentando dizer que não tem certeza de que gosta da ideia de lidar com 1 ++ como uma declaração. Pessoalmente, acho que isso não é razoável, uma vez que @Andy_Vulhop diz que 1 + = 2 é apenas uma loucura, e Ruby apenas gera um erro quando você faz isso. Portanto, 1 ++ não é mais difícil de lidar. Possivelmente, a necessidade do analisador de lidar com esse tipo de açúcar sintático é indesejável.
precisa saber é o seguinte
28

Uma razão é que até agora todo operador de atribuição (ou seja, um operador que altera uma variável) possui um =. Se você adicionar ++e --, esse não é mais o caso.

Outra razão é que o comportamento ++e --muitas vezes confunde as pessoas. Caso em questão: o valor de retorno do i++seu exemplo seria realmente 1, não 2 (o novo valor de iseria 2, no entanto).

sepp2k
fonte
4
Mais do que qualquer outra razão até agora, o racional de que "todas as tarefas têm um =neles" parece fazer sentido. Eu posso respeitar isso como uma adesão feroz à consistência.
Andy_Vulhop
e quanto a isso: a.capitalize! (atribuição implícita de a)
Luís Soares
1
@ LuísSoares a.capitalize!não reatribui a, irá alterar a string a que ase refere. Outras referências à mesma sequência serão afetadas e, se você fizer isso a.object_idantes e depois da chamada capitalize, obterá o mesmo resultado (nenhum dos quais seria verdadeiro se você o fizesse a = a.capitalize).
sepp2k
1
@ LuísSoares Como eu disse, a.capitalize!afetará outras referências à mesma string. Essa é uma diferença muito prática. Por exemplo, se você tem def yell_at(name) name.capitalize!; puts "HEY, #{name}!" ende então o chama assim:, my_name = "luis"; yell_at(my_name)o valor de my_nameserá agora "LUIS", enquanto que não seria afetado se você tivesse usado capitalizee uma atribuição.
sepp2k
1
Uau. Isso é assustador ... Saber que em Java as strings são imutáveis. Mas com o poder vem a responsabilidade. Obrigada pelo esclarecimento.
Luís Soares
25

Não é convencional em idiomas OO. De fato, não existe ++no Smalltalk, a linguagem que cunhou o termo "programação orientada a objetos" (e a linguagem pela qual Ruby é mais fortemente influenciada). O que você quer dizer é que é convencional em C e linguagens que imitam muito o C. Ruby tem uma sintaxe parecida com o C, mas não é servil ao seguir as tradições do C.

Por que não está em Ruby: Matz não queria. Essa é realmente a razão final.

O motivo de tal coisa não existir no Smalltalk é porque faz parte da filosofia dominante da linguagem que atribuir uma variável é fundamentalmente um tipo de coisa diferente do que enviar uma mensagem para um objeto - está em um nível diferente. Esse pensamento provavelmente influenciou Matz na criação de Ruby.

Não seria impossível incluí-lo no Ruby - você poderia escrever facilmente um pré-processador que transforma tudo ++em +=1. mas evidentemente Matz não gostou da idéia de um operador que fizesse uma "tarefa oculta". Também parece um pouco estranho ter um operador com um operando inteiro oculto dentro dele. Nenhum outro operador no idioma funciona dessa maneira.

Mandril
fonte
1
Não acho que sua sugestão de pré-processador funcionaria; (não um especialista), mas acho que i = 42, i ++ retornará 42, onde i + = 1 retornaria 43. Estou incorreto nisso? Portanto, sua sugestão nesse caso seria usar o i ++, pois normalmente é usado o i ++, o que é muito ruim e pode causar mais danos do que benefícios.
AturSams
12

Eu acho que há outra razão: ++no Ruby não seria remotamente útil como no C e seus sucessores diretos.

A razão é a forpalavra-chave: embora seja essencial em C, é principalmente supérfluo em Ruby. A maior parte da iteração no Ruby é feita através de métodos Enumerable, como eache mapao iterar através de alguma estrutura de dados e Fixnum#timesmétodo, quando você precisa repetir um número exato de vezes.

Na verdade, até onde eu vi, na maioria das vezes +=1é usado por pessoas recém-migradas para o Ruby a partir de idiomas no estilo C.

Em resumo, é realmente questionável se métodos ++e --seriam usados.

Mladen Jablanović
fonte
1
Esta é a melhor resposta imho. O ++ é frequentemente usado para iteração. Ruby não incentiva esse tipo de iteração.
AturSams
3

Acho que o raciocínio de Matz para não gostar deles é que ele realmente substitui a variável por uma nova.

ex:

a = SomeClass.new
def a.go
  'Olá'
fim
# neste momento, você pode chamar a.go
# mas se você fez um a ++
# que realmente significa a = a + 1
# para que você não possa mais chamar a.go
# como você perdeu o original

Agora, se alguém pudesse convencê-lo de que deveria chamar #succ! ou não, isso faria mais sentido e evitaria o problema. Você pode sugerir isso no ruby ​​core.

rogerdpack
fonte
9
"Você pode sugerir isso no ruby ​​core" ... Depois de ler e entender os argumentos em todos os outros threads em que foi sugerido na última vez, e no tempo anterior a isso, e no tempo anterior a isso, e no período anterior, e o tempo antes disso, e ... eu não estou na comunidade Ruby há muito tempo, mas apenas durante o meu tempo, lembro de pelo menos vinte dessas discussões.
Jörg W Mittag
3

Você pode definir um .+operador de auto incremento:

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

Mais informações sobre "variável de classe" estão disponíveis em " Variável de classe para incrementar objetos Fixnum ".

Sony Santos
fonte
2

E nas palavras de David Black de seu livro "The Rubyist Well-Grounded":

Alguns objetos no Ruby são armazenados em variáveis ​​como valores imediatos. Isso inclui números inteiros, símbolos (que se parecem com: this) e os objetos especiais true, false e nil. Quando você atribui um desses valores a uma variável (x = 1), a variável mantém o próprio valor, em vez de uma referência a ele. Em termos práticos, isso não importa (e muitas vezes será deixado implícito, e não explicitado repetidamente, nas discussões de referências e tópicos relacionados neste livro). Ruby lida com a desreferenciação de referências de objetos automaticamente; você não precisa fazer nenhum trabalho extra para enviar uma mensagem para um objeto que contenha, por exemplo, uma referência a uma string, em oposição a um objeto que contenha um valor inteiro imediato. Mas a regra de representação de valor imediato tem algumas ramificações interessantes, especialmente quando se trata de números inteiros. Por um lado, qualquer objeto que é representado como um valor imediato é sempre exatamente o mesmo, não importa quantas variáveis ​​sejam atribuídas. Há apenas um objeto 100, apenas um objeto falso, e assim por diante. A natureza imediata e única das variáveis ​​de número inteiro está por trás da falta de operadores de pré e pós-incremento do Ruby - ou seja, você não pode fazer isso no Ruby: x = 1 x ++ # Não existe esse operador O motivo é que, devido a para a presença imediata de 1 em x, x ++ seria como 1 ++, o que significa que você mudaria o número 1 para o número 2 - e isso não faz sentido. não importa quantas variáveis ​​sejam atribuídas. Há apenas um objeto 100, apenas um objeto falso, e assim por diante. A natureza imediata e única das variáveis ​​de número inteiro está por trás da falta de operadores de pré e pós-incremento do Ruby - ou seja, você não pode fazer isso no Ruby: x = 1 x ++ # Não existe esse operador O motivo é que, devido a para a presença imediata de 1 em x, x ++ seria como 1 ++, o que significa que você mudaria o número 1 para o número 2 - e isso não faz sentido. não importa quantas variáveis ​​sejam atribuídas. Há apenas um objeto 100, apenas um objeto falso, e assim por diante. A natureza imediata e única das variáveis ​​de número inteiro está por trás da falta de operadores de pré e pós-incremento do Ruby - ou seja, você não pode fazer isso no Ruby: x = 1 x ++ # Não existe esse operador O motivo é que, devido a para a presença imediata de 1 em x, x ++ seria como 1 ++, o que significa que você mudaria o número 1 para o número 2 - e isso não faz sentido.

Alexander Swann
fonte
Mas como você pode "1.próximo" então?
Magne
1

Não foi possível obter isso adicionando um novo método ao fixnum ou à classe Integer?

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

retorna 2

Os métodos "destrutivos" parecem ser anexados !para avisar possíveis usuários; portanto, adicionar um novo método chamado next!faria praticamente o que foi solicitado, ou seja.

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

retorna 2 (uma vez que o numb foi incrementado)

Obviamente, o next!método precisaria verificar se o objeto era uma variável inteira e não um número real, mas isso deveria estar disponível.

Sjerek
fonte
1
Integer#nextjá existe (mais ou menos), exceto que é chamado Integer#succ(em vez de 'sucessor'). Mas Integer#next!(ou Integer#succ!) seria um absurdo: lembre-se de que os métodos funcionam em objetos , não em variáveis ; portanto, numb.next!seria exatamente igual a 1.next!, ou seja, mudaria 1 para ser igual a 2 . ++seria marginalmente melhor, pois poderia ser um açúcar sintático para uma tarefa, mas pessoalmente prefiro a sintaxe atual em que todas as tarefas são concluídas =.
philomory
Para concluir o comentário acima: e Integer#predrecuperar o antecessor.
Yoni
-6

Verifique estes operadores da família C no irb de Ruby e teste-os:

x = 2    # x is 2
x += 2   # x is 4
x++      # x is now 8
++x      # x reverse to 4
Aung Zan Baw
fonte
3
Isso está claramente errado e não funciona, como (x++)é uma declaração inválida no Ruby.
precisa