Ruby sem method overloading?

Poxa, isso parece bem estranho para quem está vindo do Java.

Quero dois métodos:

on(string1, string2)

on(hash)

Mas como ruby não tem overload de métodos, eu tenho que fazer o seguinte hack:

def on(string1, string2 = nil)

    if string2 == nil then
     
         raise "Problemas..." if !string1.is_a?(Hash)

         # trabalhe com o hash aqui...

    else

         # trabalhe com duas strings aqui

     end
end

Isso é assim mesmo?

Você não precisa fazer esse hack, em Ruby normalmente se usa um Hash como parâmetro, dependendo das chaves que estiverem lá você faz uma coisa ou outra.

Dê uma olhada no modo que Rails faz renderização de páginas/pedaços, é um bom exemplo disso.

[quote=Maurício Linhares]Você não precisa fazer esse hack, em Ruby normalmente se usa um Hash como parâmetro, dependendo das chaves que estiverem lá você faz uma coisa ou outra.

Dê uma olhada no modo que Rails faz renderização de páginas/pedaços, é um bom exemplo disso.[/quote]

Mas as vezes eu quero ou um Hash ou um par apenas, ou seja,

on(Hash)

ou

on(String, String)

Nesse caso eu fiz certo ou errado?

No caso do Array fica mais fácil, pois é apenas um parametro:

on(Array)

ou

on(String)

Daí um if para checar se é array resolve, mas no caso do Hash tive que fazer aquele hack lá…

Não é que você tenha feito certo ou errado, na verdade, nesse seu caso só com dois parâmetros, deixar um deles opcional não mata, o que é feio é deixar um segundo parâmetro opcional quando o primeiro parâmetro é um Hash.

Em Ruby, se você declara um método assim um método assim:

def metodo( options ) # faz blablabla end

E chama ele assim:

objeto.metodo( :string1 => 'primeiro string', :string2 => 'segundo string' )

Esse hash já vira o seu parâmetro options, então é mais simples do que fazer o cara declarar dois parâmetros. Imagine que colocar um hash como o primeiro parâmetro de um método que ainda vai ter outro parâmetro é tão estranho quando ter um vararg e depois ter uma variável qualquer.

Em Ruby isso não é proibido, mas também não é natural, você não vai ver bibliotecas fazendo isso, a não ser em casos bem específicos.

Mais uma vez, olhar o método render e os seus diversos usos é um bom ponto de partida pra isso.

Entendi. Não sabia que dava para fazer assim:

on( :SUCCESS => "asdfsf" , :ERROR => "adfasf")

Pensei que tinha que fazer assim:

on( { :SUCCESS => "asdfsf" , :ERROR => "adfasf" } )

Valeu!

Desenterrando…

Ei Maurício, e essa forma é muito utilizada?

[code]def test(*args)
if args.size == 1
puts '1’
elsif args.size == 2
puts '2’
end
end

test(1)
test(1, 2)[/code]

Rapaz, eu acho isso “feio” e é bem incomum. Pra falar a verdade eu só vi isso em um código que eu mesmo escrevi pra fazer umas mágicas entre os link_to e as rotas nomeadas, como os métodos são gerados em tempo de execução e eles tem que ser inteligentes pra saber pra quem entregar a geração do caminho da URL pro link_to eu tive que fazer uma coisa parecida com isso, mas se você não estiver fazendo uma “mágica” é melhor ficar longe desse tipo de coisa, eu acho hashes bem mais limpos e fáceis de se entender.

[quote=saoj]
Entendi. Não sabia que dava para fazer assim:

on( :SUCCESS => "asdfsf" , :ERROR => "adfasf")

Pensei que tinha que fazer assim:

on( { :SUCCESS => "asdfsf" , :ERROR => "adfasf" } )

Valeu![/quote]
saoj, maiúsculas em ruby são utilizadas apenas para constantes …
usa las como nomes de parâmetros assim fica muito extranho …

acho que uma idéia interessante para este caso seria utilizar o method_missing para a sintaxe ficar mais ou menos assim:

on_success 'asdads'

on_XXXX 'asddases'

Por exemplo, interceptar as chamadas para metodos não existentes iniciados com “on_”, parecido com o que o ActiveRecord faz com os metodos iniciados em “find_”

[quote=Maurício Linhares]… em Ruby normalmente se usa um Hash como parâmetro, dependendo das chaves que estiverem lá você faz uma coisa ou outra.


[/quote]

Defensores ferrenhos de Ruby podem me bater. :smiley:
Mas implementar um overloading desta maneira é gambiarra!

[quote=xandroalmeida][quote=Maurício Linhares]… em Ruby normalmente se usa um Hash como parâmetro, dependendo das chaves que estiverem lá você faz uma coisa ou outra.


[/quote]

Defensores ferrenhos de Ruby podem me bater. :smiley:
Mas implementar um overloading desta maneira é gambiarra!
[/quote]
Me desculpe mas isso não é uma maneira de implementar overloading. É apenas obter o mesmo resultado que overloading oferece, porém de outra forma.

A propósito, não considero overloading um recurso OO (polimorfismo), caso alguém venha bater dizendo que Ruby é menos OO por não ter essa opção.

[quote=xandroalmeida]Defensores ferrenhos de Ruby podem me bater. :smiley:
Mas implementar um overloading desta maneira é gambiarra!
[/quote]

Bem, então estude Ruby, entenda porque a linguagem não tem overloading de método e explique pra nós porque overloading de método é uma coisa interssante :slight_smile:

Não é o fato de ser defensor ou não, a linguagem não tem essa característica (e existem motivos bem simples pra isso, como parâmetros opcionais em métodos e a inexistência de declaraçõs de tipos de variáveis) e tentar fazer isso em Ruby é tentar programar em outra linguagem em Ruby.

Mais uma vez, quem estiver interessado em entender o “Ruby-way”, dê uma olhada no jeito com rails faz a renderização de suas páginas, dá pra ter uma idéia legal de como se faz isso.

Eh uma maneira de fazer o esquema dos named parameters do smalltalk e objective C:

[code]// Smalltalk
Rectangle width: 100 height: 200

// Ruby
Rectangle.new :width => 100, :height => 200

// Java
new Rectangle(100, 200)
[/code]

Repara que, sem olhar pra assinatura do metodo, eu nao sei qual eh width e qual eh height em Java. Pra isso, eu teria que bolar algo do tipo:

…que ateh que eh bonitinho, mas nada nada nada idiomatico.

Eu esperava a porrada mais forte do cv :smiley:

Se não fosse importante não teria que ser “emulado” em Ruby. Então acho que não preciso ficar explicando a importância da coisa. Não seja radical dizendo que algo não é importante apenas porque Ruby não implementa.

No mais, não concordo com parâmetros em hash e continuo achando que usar isso é gambi. Até entendo a preferencia pela legibilidade exposta pelo cv, mas o código vai ficar cheios de if else

Acredito que o uso de parâmetros opcionais seja uma forma um pouco mais limpa de fazer a coisa.

O fato não é ser ou não importante porque Ruby implementa ou não, é porque sobrecarga de métodos é simplesmente um monte de métodos diferentes que tem o mesmo nome, não tem nada de especial nisso, eu, pessoalmente, acho que isso complica muito mais do que facilita o desenvolvimento, já que esses métodos são resolvidos em tempo de compilação pelo tipo da variável que está sendo passada, então você já sabia de qualquer forma qual o método que deveria ser chamado.

Mas de qualquer forma, não faz sentido existir sobrecarga de métodos em uma linguagem que não é estaticamente tipada. A não ser que você faça uma coisa viajante como em Groovy, onde o binding de parâmetros é feito pelo tipo real que está na referência e não pelo tipo da referência, mas como em Ruby não existe declaração de tipos, isso não existe.

[quote=xandroalmeida]No mais, não concordo com parâmetros em hash e continuo achando que usar isso é gambi. Até entendo a preferencia pela legibilidade exposta pelo cv, mas o código vai ficar cheios de if else

Acredito que o uso de parâmetros opcionais seja uma forma um pouco mais limpa de fazer a coisa.[/quote]

Parâmetros opcionais dão na mesma (se tiver esse parâmetro, faz x, se não tiver, faz y) e ainda são menos explícitos do que os hashes e ainda tem uma complicação a mais que é a questão do posicionamento, usando hashes você não precisa se preocupar com a posição dos parâmetros, com parâmetros opcionais você precisa.

Um exemplo bem simples, uma função que faça a paginação de consultas no banco de dados:

def paginate( conditions, page = 1, limit = 10)
    # faz qualquer coisa
end

#chamando a função pra página 2, até aqui tudo bem
paginate( 'event_id = 1', 2)

#mas e se eu quiser os primeiros 20 resultados?
paginate( 'event_id = 1', 1, 20)
#tive que repetir o valor do parametro page, mesmo mandando o mesmo valor

Fazendo assim eu teria todos os problemas resolvidos e ainda teria uma sintaxe mais amigável:

def paginate( conditions, options = {} )
     options = {:page => 1, :limit => 10}.merge(options)
end

paginate( 'event_id = 1', :page => 2)

paginate( 'event_id = 1', :limit => 30)

concordo com o Maurício. Ruby não tem tipagem explícita de tipos, então não faz sentido sobrecarga!

Cheio de if/else ai na sua cabeca. :wink:

[code] def foo(args)
bar = args.delete :bar
baz = args.delete :baz
bar * baz
end

foo :bar => 2, :baz => 2 # 4
[/code]

Isso é bonito, mas nesse caso vc está assumindo que será sempre dois parametros.

Quando eu tiver 2 possibilidades de métodos diferentes, de acordo com o número de parametros (java method overloading), então eu terei que ter um if para discriminar entre uma coisa ou outra, não?

Er, sim. Mas isso so te indica que:

  1. vc nao ta sendo la muito criativo ao dar nomes pros seus metodos
  2. o seu metodo provavelmente faz mais de uma coisa
  3. vc tem parametros opcionais e nao esta sabendo expressar isso na sua ‘assinatura’

Cheio de if/else ai na sua cabeca. :wink:

[code] def foo(args)
bar = args.delete :bar
baz = args.delete :baz
bar * baz
end

foo :bar => 2, :baz => 2 # 4
[/code][/quote]

Bem, vamos lá.
A sobrecarga de forma a ter diferente métodos para diferentes tipos de parâmetros é algo estranho/dificil para uma linguagem dinâmica. Isso eu consegui enxergar durante este tópico. Eu acho que isso é uma vantagem para linguagens com tipagem statica.

Ainda acho uma boa ter diferentes métodos para quantidade de parâmetros diferentes, mas á algo que da para viver bem.

Mas o ponto que eu acho gambi é exatamente o exemplo do cv.
Mata-se a assinatura do método, ok fica legível a chamada do método, mas para quem precisa usar um método deste e olha a sua assinatura, fica totalmente perdido.

E fora que tenho que ficar pegando os parâmetros da chamada na unha como no próprio exemplo dado pelo cv

bar = args.delete :bar
baz = args.delete :baz 

PS: Isto me lembra quando programava em Assembly e tinha que obter os parâmetros na pilha.

E oque acontece se eu chamar

foo :bar => 2

Se eu quiser ter um tratamento diferente para o caso de quando não é passado o baz eu tenho que ter um if else

Essencialmente, o que se faz eh trocar a legibilidade da assinatura pela legibilidade da chamada. Eu prefiro uma chamada mais facil de ler, pq assim eu nao tenho que decorar assinaturas ou saber qual metodo esta sendo chamado. No caso do Ruby, a coisa funciona bem mais como passagem de mensgens do que chamada de metodos, o que eu considero bem saudavel (e ajuda a pensar de forma a desacoplar mais o codigo).

[quote=xandroalmeida]E fora que tenho que ficar pegando os parâmetros da chamada na unha como no próprio exemplo dado pelo cv
(…)
PS: Isto me lembra quando programava em Assembly e tinha que obter os parâmetros na pilha.[/quote]

Ficar “pegando na unha” nao eh la a maior das dificuldades, nao polui tanto o codigo assim (mas concordo que seria melhor se fosse “de verdade”, como em Smalltalk/ObjC). De qualquer forma, vc provavelmente esta bastante acostumado com esse padrao (request.getParameter e request.getAttribute, alguem?)

E o caso nao eh sempre esse? Em Java vc tem um metodo overloaded, mas no fundo no fundo, isso nao deixa de ser um if/else em algum lugar.

IMHO poderia ser melhor, mas o idioma continua sendo bem pratico e facilita a legibilidade bastante.