class Ruby; include Smalltalk; end

2007 December 02, 15:15 h - tags: smalltalk beginner learning ruby

Não, não! Não é mais um daqueles artigos “Ruby vs Smalltalk”. Estava hoje lendo um fórum e caí nesse dilema novamente. Quando se fala que Smalltalk é mais orientado a objetos que Ruby o exemplo mais usado é que em Ruby fazemos condicionais de maneira imperativa (uma das maneiras):

1
2
3
4
5
a = if expression
  1
else
  2
end

E em Smalltalk se faz passando mensagens ao objeto True ou False:

expression ifTrue:[ a := 1 ] ifFalse:[ a := 2 ]

1
2
3
4
5
6
7
8
9
Onde ifTrue e ifFalse são representados como se fossem 'envio de mensagens' ao objeto true ou false. A primeira diferença é que se o objeto não for true ou false isso normalmente pode dar uma exception do tipo MustBeBoolean. Em Ruby tudo é true e apenas os objetos 'nil' e 'false' são considerados 'não-true'.

Outra coisa é que em Smalltalk podemos enviar várias mensagens ao mesmo objeto de uma só vez, em Ruby o método precisa devolver a si mesmo para encadear mensagens, mas fica parecido. 

Então, e se em Ruby pudéssemos fazer assim:

--- ruby
expression.if_true { a = 1 }.if_false { a = 2 }

Hm, acontece que podemos! Vejamos como ficaria isso:

1
2
3
4
5
6
7
8
9
10
11
class Object
  def if_true(&block)
    block.call if self
    self
  end

  def if_false(&block)
    block.call unless self
    self
  end
end

Podemos fazer isso graças aos blocos, que expliquei em detalhes no artigo anterior. Além disso já sabemos que todas as classes no Ruby são abertas, em especial a Object que é a pai de todas. Nesse caso todas as classes do Ruby passam a ter esta funcionalidade, não apenas TrueClass ou FalseClass.

Vejamos como ficaria com a notação de do..end em vez de chaves:

1
2
3
4
5
expression.if_true do
  a = 1
end.if_false do
  a = 2
end

Feinha.

Isso é apenas um exercício de metaprogramação no Ruby porque esta implementação não tem absolutamente nenhuma vantagem sobre a notação nativa de if; else; end. Como apontado em inúmeras discussões a respeito como esta a notação imperativa atual do Ruby não é ambígua e o interpretador não precisa trabalhar dispatch de métodos. Em algumas implementações de Smalltalk ifTrue;ifFalse é mais um syntatic sugar porque internamente eles também não fazem dispatching de métodos e fazem o branch condicional normal. Motivo: performance.

Existe outra diferença, em Ruby e Smalltalk podemos enviar múltiplos blocos a um mesmo método, mas Smalltalk tem uma sintaxe mais simples para isso, como é o caso do próprio ifTrue;ifFalse. No fundo o efeito é o mesmo. Se eu quiser muito mesmo posso não usar o ‘if’ imperativo do Ruby, mas não há nenhum ganho ao fazer isso. O Ruby poderia colocar um ‘syntatic sugar’ semelhante que permitisse usar a notação ‘puramente de objetos’ mas que internamente reconvertesse num if imperativo para não impactar a performance.

Outra coisa que Ruby faz de maneira ‘imperativa’: herança de classes. Em Ruby fazemos assim:

1
2
class Carro < Veiculo
end

Em Smalltalk (sem usar a IDE) se faz:

Veiculo subclass: #Carro

1
2
3
4
5
6
7
8
9
10
11
Ok, ok, se realmente quisermos fazer assim em Ruby podemos usar mais metaprogramação:

--- ruby
class Object
  def self.subclass name
    eval "class #{name} < #{self}; end"
  end
end

Veiculo.subclass :Carro

Pronto, conceitualmente parecido novamente. As características de metaprogramação de Ruby nos permitem criar praticamente qualquer nova sintaxe e é o motivo dele ser uma linguagem tão poderoda para Domain Specific Languages (DSL). E nunca se esqueçam da maior diferença: Smalltalk não é apenas uma linguagem, é um ambiente, coisa que Ruby não é e não planeja ser. Então não se trata apenas de sintaxe, são filosofias completamente diferente de programação que vai além de simples ‘ser OOP’ ou não.

No fundo, tanto faz, pessoalmente prefiro algo pragmático do que apenas ‘intelectualmente puro’, e nesse caso tanto Ruby quanto Smalltalk se encaixam, pois ambos fazem compromissos em alguma das etapas. Existem mais alguns Ruby vs Smalltalk interessantes, mas não os leve muito a sério a menos que você pretenda se engajar na tarefa de ajudar a codificar o compilador do Ruby. Caso contrário whatever.

Update: Na realidade, para ficar um pouco mais parecido com o jeito Smalltalk de passar dois blocos ne mesma mensagem, a versão Ruby deveria ser parecida com esta:

1
2
3
4
5
6
7
8
9
10
11
12
class Object
  def if(true_block = nil, false_block = nil)
    if self
      true_block.call if true_block
    else
      false_block.call if false_block
    end
  end
end

n = 10
puts ( n > 1 ).if( proc { "true" }, proc { "false" } )

Novamente, a performance decai. Comparei os tempos de 500 mil operações, na primeira vez com ‘if’ condicional e nesta versão via passagem de método e a diferença foi de 2 a 4 vezes mais devagar passando como métodos, portanto não se atenham a essas versões exóticas além de apenas servir como curiosidade acadêmica.

Comments

comentários deste blog disponibilizados por Disqus