Monkeypatching is Destroying Ruby

[quote][…] To the point that smart, experienced hackers reach for a monkey patch as their tool of first resort, even when a simpler, more traditional solution is possible.

I don?t believe this situation to be sustainable. Where I work, we are already seeing subtle, difficult-to-debug problems crop up as the result of monkey patching in plugins. Patches interact in unpredictable, combinatoric ways. And by their nature, bugs caused by monkey patches are more difficult to track down than those introduced by more traditional classes and methods. As just one example: on one project, it was a known caveat that we could not rely on class inheritable attributes as provided by ActiveSupport. No one knew why. Every Model we wrote had to use awkward workarounds. Eventually we tracked it down in a plugin that generated admin consoles. It was overwriting Class.inherited(). It took us months to find this out.[/quote]

Eu acho que monkey patching é algo abusado em Ruby. O problema é que o esquema de bindings em Ruby é muito ruim, você não tem como criar blocos de escopo arbitrários ou fazer como em Scala e limitar o escopo de um patch. Eu já tive problemas sérios com 1.minute e conflito entre patches.

O pessoal do zope/plone é um ótimo exemplo de onde monkey patching pode levar. Na minha opnião utilizar isso é prática ruim e detona não só ruby mas qualque linguagem que suporte isso.

Sei la, eu acho a alternativa bem pior.

Mas qual seria a alternativa?

Em Ruby não dá para fazer muito. Você bsicamente pode usar monkey patching para alterar as classes core ou fazer algo no estilo Java onde você tem uma gerência de dependências estáticas, onde um inteiro não pode ser independente de Date em compile time e integrado em runtime.

O problema é que as reras de escopo em Ruby são arcaicas considerando as capacidades de metaprogramação da linguagem. Tudo é muito global e muito irreversível.

Algo como cflow e cflowbelow do AspectJ já me deixariam feliz :slight_smile:

Scala tem uma ótima saída para isso, com conversões implícitas, e Lisp tem bloco léxicos hierárquicos com LET há décadas. Acho que simplesmente ningu’m pensou que ia precisar disso e como consequência você tem monkey patching e namespace wars.

Ruby tem duas coisas que melam a vida de quem quer fazer metaprogramação mais hardcode. Não existe como sobrescrever o mecanismo de resolução de nomes e tão pouco como definir escopos no qual alterações a classes acontecem.

A/C

Louds (Moderador GUJ)

Por gentileza, conforme sua explicação, postei a URL abaixo, porque não compreendi tais afirmações sobre Ruby metaprogramação, o que você quer dizer sobre mais hardcode, [b]mecanismo de resolução de nomes , etc …

:idea: Poderia finalizar colocando melhor transparência ao assunto, sitando melhores detalhes sobre tal concepção.[/b]

:arrow: http://www.guj.com.br/posts/list/83661.java

Att.
Marcio Duran

Simples, crocodilo, como você faz e quiser definir um método seu, que você criou e faz algo que só você precisa, chamado “times” em Fixnum se você usa Rails?

intance_eval tem resolvido bem a minha vida:

def ei_vc(&bloco)
  MeuEscopo.new.instance_eval(&bloco)
end

ei_vc do
  qualquer
  coisa
end

class MeuEscopo
  def qualquer
    # ...
  end
  def coisa
    # ...
  end
  # ...
end

A/C
Louds (Moderador GUJ)
Por gentileza, conforme sua explicação, postei a URL abaixo, porque não compreendi tais afirmações sobre Ruby metaprogramação,[/quote]

O Louds está falando em um nível que é umas duas ordens de grandeza maiores que você pode compreender. Se quiser entender melhor o que ele fala, por favor, sr. Duran, aprenda outras linguagens de programação e um bocadinho de matemática, para que você não reduza Ruby a uma mera “linguagem orientada a objetos”, com você postou aqui no GUJ :stuck_out_tongue:

E leia um pouco mais - sitar é um instrumento musical de origem árabe; acredito que você queria dizer “citar”.

[jaba mode on]
Meu comentário sobre isso ficou muito grande e acabei colocando ele no meu blog
http://www.kumpera.net/blog/index.php/2008/03/03/monkeypatching-gambiarra-du-jour/
[jaba mode off]

intance_eval tem resolvido bem a minha vida:
[/quote]

Aind anão adianta. Em outro tópico eu citei uma DSL de testes usando RSpec e Selenium, nessa DSL tivemos que azer os objetos de domínio incluírem módulos do RSpec para que pudéssemos usar pending e outros métodos dentro dos nossos blocos que não são closures.

Ruby precisava ter bindings hierárquicos. Hoje em Ruby você so consegue avaliar algo em dois níveis, o primeiro (como intance_eval) e o Object/Kernel.

e vc não consegue fazer isso decorando os caras só no escopo do bloco?

algo como o jmock faz em java:

{
  one(obj).pending()...
}

Tudo bem, pode dar uma trabalheira, mas acho que funciona.

Desculpa, Fábio, boiei. Decorando o que?

intance_eval tem resolvido bem a minha vida:
[/quote]

Aind anão adianta. Em outro tópico eu citei uma DSL de testes usando RSpec e Selenium, nessa DSL tivemos que azer os objetos de domínio incluírem módulos do RSpec para que pudéssemos usar pending e outros métodos dentro dos nossos blocos que não são closures.

Ruby precisava ter bindings hierárquicos. Hoje em Ruby você so consegue avaliar algo em dois níveis, o primeiro (como intance_eval) e o Object/Kernel.[/quote]

Não precisa ter binding hierárquico, basta ter binding extensível.

code = proc {|a| print a.d }

context_eval(proc) do |obj, name| 
 return "d é mais legal" if name == :d
 obj.send (name)
end

Com isso é trivial implementar instance_eval module_eval e por ai vai. Para completar permita configurar um bloco padrão de resolução por Modulo/classe/método e adicione a possibilidade de andar na pilha e recuperar um bloco que resolva nomes de acordo com o método em questão. Com essas três coisas basicamente tudo que reclamam sobre meta-programação em Ruby tem solução simples.

Exemplo do problema:

class A

def bazooka
 @a = "BOOOOM"
 B.new.fire {puts @a}
end
end

class B

def fire(&block)
 instance_eval &block
end

end

A.new.bazooka #boom?

Eu diria que não precisava ter binding extensível, bastava ter binding hierárquico que é mais limitado, provavelmente mais fácil de implementar e ainda assim eficiente na maioria dos casos.

Eu acho que eu to boiando. Pensei numas coisas doidas aqui, mas só consigo explicar direito vendo o que vc quer fazer.

Procurei o seu tópico da selenium dsl (e pending) e não achei (link?). Posta um snippet?