Por que o ruby ​​não suporta sobrecarga de método?

146

Em vez de suportar a sobrecarga de métodos, o Ruby substitui os métodos existentes. Alguém pode explicar por que o idioma foi projetado dessa maneira?

Rambo
fonte

Respostas:

166

A sobrecarga de método pode ser alcançada declarando dois métodos com o mesmo nome e assinaturas diferentes. Essas assinaturas diferentes podem ser,

  1. Argumentos com diferentes tipos de dados, por exemplo: method(int a, int b) vs method(String a, String b)
  2. Número variável de argumentos, por exemplo: method(a) vs method(a, b)

Não é possível obter sobrecarga de método usando a primeira maneira, porque não há declaração de tipo de dados no ruby ​​( linguagem de tipo dinâmica ). Portanto, a única maneira de definir o método acima édef(a,b)

Com a segunda opção, pode parecer que podemos obter sobrecarga de método, mas não podemos. Digamos que eu tenha dois métodos com número diferente de argumentos,

def method(a); end;
def method(a, b = true); end; # second argument has a default value

method(10)
# Now the method call can match the first one as well as the second one, 
# so here is the problem.

Portanto, o ruby ​​precisa manter um método na cadeia de pesquisa de métodos com um nome exclusivo.

nkm
fonte
22
A resposta de Jörg W Mittag, enterrada bem abaixo, definitivamente vale a pena ser lida.
user2398029
1
E a resposta @Derek Ekins', enterrou ainda mais, fornece uma alternativa
Cyril Duchon-Doris
Nota para futuros Rubistas ... FWIW você pode fazer isso com contratos gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave
214

"Sobrecarregar" é um termo que simplesmente nem faz sentido no Ruby. É basicamente um sinónimo de "expedição à base de argumento estático", mas Ruby não têm expedição estática em tudo . Portanto, o motivo pelo qual o Ruby não suporta despacho estático com base nos argumentos é porque não suporta despacho estático, ponto final. Ele não suporta envio estático de qualquer tipo , seja com base em argumentos ou de outra forma.

Agora, se você não está perguntando especificamente sobre sobrecarga, mas talvez sobre despacho dinâmico baseado em argumentos, a resposta é: porque Matz não o implementou. Porque ninguém mais se incomodou em propor. Porque ninguém mais se preocupou em implementá-lo.

Em geral, o envio dinâmico baseado em argumentos em uma linguagem com argumentos opcionais e listas de argumentos de comprimento variável é muito difícil de acertar e ainda mais difícil de manter compreensível. Mesmo em linguagens com despacho estático baseado em argumentos e sem argumentos opcionais (como Java, por exemplo), às vezes é quase impossível dizer para um mero mortal, qual sobrecarga será detectada.

No C #, você pode realmente codificar qualquer problema 3-SAT na resolução de sobrecarga, o que significa que a resolução de sobrecarga no C # é NP-difícil.

Agora tente isso com despacho dinâmico , onde você tem a dimensão de tempo adicional para se lembrar.

Existem linguagens que são enviadas dinamicamente com base em todos os argumentos de um procedimento, em oposição às linguagens orientadas a objetos, que são enviadas apenas no selfargumento zeroth "oculto" . O Lisp comum, por exemplo, despacha os tipos dinâmicos e até os valores dinâmicos de todos os argumentos. Clojure despacha em uma função arbitrária de todos os argumentos (que BTW é extremamente legal e extremamente poderoso).

Mas não conheço nenhuma linguagem OO com despacho dinâmico baseado em argumentos. Martin Odersky disse que pode considerar adicionar expedição baseada em argumento ao Scala, mas apenas se ele puder remover a sobrecarga ao mesmo tempo e for compatível com versões anteriores, tanto com o código Scala existente que usa sobrecarga e compatível com Java (ele mencionou especialmente Swing e AWT que desempenham alguns truques extremamente complexos, exercitando praticamente todos os casos desagradáveis ​​do canto escuro das regras de sobrecarga bastante complexas do Java). Eu mesmo tive algumas idéias sobre como adicionar expedição baseada em argumento ao Ruby, mas nunca consegui descobrir como fazê-lo de uma maneira compatível com versões anteriores.

Jörg W Mittag
fonte
5
Esta é a resposta certa. A resposta aceita simplifica demais. O C # nomeou parâmetros e parâmetros opcionais e ainda implementa sobrecarga, portanto, não é tão simples como " def method(a, b = true)não funcionará, portanto, a sobrecarga de método é impossível". Não é; é apenas difícil. Achei ESTA resposta realmente informativa, no entanto.
precisa saber é o seguinte
1
@tandrewnichols: só para se ter uma idéia de como a resolução de sobrecarga é "difícil" em C # ... é possível codificar qualquer problema de 3-SAT como resolução de sobrecarga em C # e fazer com que o compilador resolva em tempo de compilação, tornando a resolução de sobrecarga em C # NP -hard (3-SAT é conhecido por ser NP-completo). Agora imagine ter que fazer isso não uma vez por site de chamada em tempo de compilação, mas uma vez por chamada de método para cada chamada de método em tempo de execução.
Jörg W Mittag
1
@ JörgWMittag Você poderia incluir um link mostrando uma codificação de um problema de 3-SAT no mecanismo de resolução de sobrecarga?
Squidly 26/08/2015
2
Parece que "sobrecarregar" é sinônimo de "envio estático baseado em argumentos". O envio estático baseado em argumentos é simplesmente a implementação mais comum de sobrecarga. Sobrecarga é um termo independente de implementação que significa "mesmo nome de método, mas implementações diferentes no mesmo escopo".
snovity
85

Presumo que você esteja procurando a capacidade de fazer isso:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

O Ruby suporta isso de uma maneira diferente:

def my_method(*args)
  if args.length == 1
    #method 1
  else
    #method 2
  end
end

Um padrão comum também é passar opções como um hash:

def my_method(options)
    if options[:arg1] and options[:arg2]
      #method 2
    elsif options[:arg1]
      #method 1
    end
end

my_method arg1: 'hello', arg2: 'world'

espero que ajude

Derek Ekins
fonte
15
+1 por fornecer o que muitos de nós apenas queremos saber: como trabalhar com um número variável de argumentos nos métodos Ruby.
precisa saber é o seguinte
3
Esta resposta pode se beneficiar de informações adicionais sobre argumentos opcionais. (E talvez também chamado argumentos, agora que aqueles são uma coisa.)
Ajedi32
9

A sobrecarga de método faz sentido em um idioma com digitação estática, onde você pode distinguir entre diferentes tipos de argumentos

f(1)
f('foo')
f(true)

bem como entre diferentes números de argumentos

f(1)
f(1, 'foo')
f(1, 'foo', true)

A primeira distinção não existe no rubi. Ruby usa digitação dinâmica ou "digitação de pato". A segunda distinção pode ser tratada por argumentos padrão ou trabalhando com argumentos:

def f(n, s = 'foo', flux_compensator = true)
   ...
end


def f(*args)
  case args.size
  when  
     ...
  when 2
    ...
  when 3
    ...
  end
end
bjelli
fonte
Isso não tem nada a ver com digitação forte. Afinal, Ruby é fortemente digitado.
Jörg W Mittag
8

Isso não responde à pergunta de por que o ruby ​​não possui sobrecarga de método, mas as bibliotecas de terceiros podem fornecê-lo.

A biblioteca contract.ruby permite sobrecarga. Exemplo adaptado do tutorial:

class Factorial
  include Contracts

  Contract 1 => 1
  def fact(x)
    x
  end

  Contract Num => Num
  def fact(x)
    x * fact(x - 1)
  end
end

# try it out
Factorial.new.fact(5)  # => 120

Observe que isso é realmente mais poderoso que a sobrecarga do Java, porque você pode especificar valores para corresponder (por exemplo 1), não apenas tipos.

Você verá um desempenho reduzido usando isso; você precisará executar benchmarks para decidir quanto pode tolerar.

Kelvin
fonte
1
Em aplicativos do mundo real com qualquer tipo de IO, você terá apenas 0,1 a 10% (dependendo de que tipo de IO) de lentidão.
Waterlink
1

Costumo fazer a seguinte estrutura:

def method(param)
    case param
    when String
         method_for_String(param)
    when Type1
         method_for_Type1(param)

    ...

    else
         #default implementation
    end
end

Isso permite que o usuário do objeto use o método limpo e claro method_name: method Mas se ele deseja otimizar a execução, ele pode chamar diretamente o método correto.

Além disso, torna os testes mais claros e melhores.

Barragem
fonte
1

já existem ótimas respostas sobre por que lado da questão. no entanto, se alguém que procura outras soluções, compra a gema de rubi funcional, inspirada nos recursos de correspondência de padrões do Elixir .

 class Foo
   include Functional::PatternMatching

   ## Constructor Over loading
   defn(:initialize) { @name = 'baz' }
   defn(:initialize, _) {|name| @name = name.to_s }

   ## Method Overloading
   defn(:greet, :male) {
     puts "Hello, sir!"
   }

   defn(:greet, :female) {
     puts "Hello, ma'am!"
   }
 end

 foo = Foo.new or Foo.new('Bar')
 foo.greet(:male)   => "Hello, sir!"
 foo.greet(:female) => "Hello, ma'am!"   
Oshan Wisumperuma
fonte