Perché il rubino non supporta il sovraccarico del metodo?


146

Invece di supportare il sovraccarico del metodo, Ruby sovrascrive i metodi esistenti. Qualcuno può spiegare perché la lingua è stata progettata in questo modo?

Risposte:


166

Il sovraccarico del metodo può essere ottenuto dichiarando due metodi con lo stesso nome e firme diverse. Queste diverse firme possono essere:

  1. Argomenti con diversi tipi di dati, ad esempio: method(int a, int b) vs method(String a, String b)
  2. Numero variabile di argomenti, ad esempio: method(a) vs method(a, b)

Non è possibile ottenere il sovraccarico del metodo utilizzando il primo modo perché non esiste una dichiarazione del tipo di dati in ruby ​​( linguaggio tipizzato dinamico ). Quindi l'unico modo per definire il metodo sopra èdef(a,b)

Con la seconda opzione, potrebbe sembrare che possiamo ottenere un sovraccarico del metodo, ma non possiamo. Supponiamo che io abbia due metodi con un numero diverso di argomenti,

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.

Quindi ruby ​​deve mantenere un metodo nella catena di ricerca del metodo con un nome univoco.


22
@ Jörg W La risposta di Mittag, sepolta molto sotto, merita sicuramente una lettura.
user2398029

1
E la risposta di @Derek Ekins, seppellita ancora di più, fornisce un'alternativa
Cyril Duchon-Doris,

Nota per i rubyisti futuri ... FWIW, puoi farlo con i contratti gem egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave

214

"Sovraccarico" è un termine che semplicemente non ha nemmeno senso in Ruby. Fondamentalmente è un sinonimo di "spedizione argomento-base statica", ma Ruby non avere la spedizione statica a tutti . Quindi, il motivo per cui Ruby non supporta l'invio statico in base agli argomenti, è perché non supporta l'invio statico, punto. Non supporta l'invio statico di alcun tipo , basato su argomenti o altro.

Ora, se in realtà non stai chiedendo in modo specifico di sovraccaricare, ma forse di un invio dinamico basato su argomenti, la risposta è: perché Matz non l'ha implementato. Perché nessun altro si è preso la briga di proporlo. Perché nessun altro si è preso la briga di implementarlo.

In generale, l'invio dinamico basato su argomenti in una lingua con argomenti opzionali e liste di argomenti a lunghezza variabile, è molto difficile da ottenere, e ancora più difficile da mantenere comprensibile. Anche nei linguaggi con invio statico basato su argomenti e senza argomenti opzionali (come Java, per esempio), a volte è quasi impossibile dire per un semplice mortale quale sovraccarico verrà scelto.

In C #, puoi effettivamente codificare qualsiasi problema 3-SAT in risoluzione di sovraccarico, il che significa che la risoluzione di sovraccarico in C # è NP-difficile.

Ora provalo con l' invio dinamico , dove hai la dimensione temporale aggiuntiva da tenere in testa.

Ci sono lingue che inviano dinamicamente in base a tutti gli argomenti di una procedura, al contrario dei linguaggi orientati agli oggetti, che inviano solo selfsull'argomento zeroth "nascosto" . Il Lisp comune, ad esempio, invia i tipi dinamici e persino i valori dinamici di tutti gli argomenti. Clojure invia una funzione arbitraria di tutti gli argomenti (che BTW è estremamente interessante ed estremamente potente).

Ma non conosco nessun linguaggio OO con invio dinamico basato su argomenti. Martin Odersky ha affermato che potrebbe prendere in considerazione l'aggiunta di spedizioni basate su argomenti a Scala, ma solo se è in grado di rimuovere contemporaneamente il sovraccarico ed essere retrocompatibile sia con il codice Scala esistente che utilizza il sovraccarico sia compatibile con Java (ha menzionato in particolare Swing e AWT che svolgono alcuni trucchi estremamente complessi esercitando praticamente ogni brutto caso di dark corner delle piuttosto complesse regole di sovraccarico di Java). Ho avuto alcune idee su come aggiungere un invio basato su argomenti a Ruby, ma non sono mai riuscito a capire come farlo in un modo retrocompatibile.


5
Questa è la risposta esatta. La risposta accettata semplifica eccessivamente. C # ha nomi e parametri opzionali e implementa ancora il sovraccarico, quindi non è così semplice come " def method(a, b = true)non funzionerà, quindi il sovraccarico del metodo è impossibile". Non è; è solo difficile. Ho trovato QUESTA risposta davvero istruttiva, comunque.
Tandrewnichols,

1
@tandrewnichols: solo per dare un'idea di quanto "difficile" sia la risoluzione di sovraccarico in C # ... è possibile codificare qualsiasi problema 3-SAT come risoluzione di sovraccarico in C # e fare in modo che il compilatore lo risolva al momento della compilazione, rendendo così la risoluzione di sovraccarico in C # NP -hard (3-SAT è noto per essere NP-completo). Ora immagina di doverlo fare non una volta per sito di chiamata al momento della compilazione ma una volta per chiamata di metodo per ogni singola chiamata di metodo in fase di esecuzione.
Jörg W Mittag,

1
@ JörgWMittag Potresti includere un collegamento che mostra una codifica di un problema 3-SAT nel meccanismo di risoluzione del sovraccarico?
Calamari,


2
Non sembra che "sovraccarico" sia sinonimo di "invio statico basato su argomenti". L'invio statico basato su argomenti è semplicemente l'implementazione più comune del sovraccarico. Il sovraccarico è un termine agnostico di implementazione che significa "stesso nome di metodo ma implementazioni diverse nello stesso ambito".
snovity

85

Presumo che tu stia cercando la possibilità di farlo:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby lo supporta in un modo diverso:

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

Un modello comune è anche quello di passare le opzioni come 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'

spero che aiuti


15
+1 per fornire ciò che molti di noi vogliono solo sapere: come lavorare con un numero variabile di argomenti nei metodi Ruby.
ashes999,

3
Questa risposta potrebbe beneficiare di ulteriori informazioni su argomenti opzionali. (E forse anche argomenti chiamati, ora che quelli sono una cosa.)
Ajedi32

9

Il sovraccarico del metodo ha senso in un linguaggio con tipizzazione statica, in cui è possibile distinguere tra diversi tipi di argomenti

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

nonché tra diversi numeri di argomenti

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

La prima distinzione non esiste nel rubino. Ruby utilizza la digitazione dinamica o "digitazione anatra". La seconda distinzione può essere gestita da argomenti predefiniti o lavorando con argomenti:

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


def f(*args)
  case args.size
  when  
     ...
  when 2
    ...
  when 3
    ...
  end
end

Questo non ha nulla a che fare con la digitazione forte. Dopo tutto Ruby è fortemente tipizzato.
Jörg W Mittag,

8

Questo non risponde alla domanda sul perché ruby ​​non abbia un metodo di overload, ma le librerie di terze parti possono fornirlo.

La libreria contract.ruby consente il sovraccarico. Esempio adattato dal 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

Si noti che questo è in realtà più potente del sovraccarico di Java, perché è possibile specificare valori da abbinare (ad es. 1), Non semplicemente tipi.

Vedrai prestazioni ridotte usando questo però; dovrai eseguire benchmark per decidere quanto puoi tollerare.


1
Nelle applicazioni del mondo reale con qualsiasi tipo di I / O, si avrà solo un rallentamento dello 0,1-10% (a seconda del tipo di I / O).
Waterlink

1

Faccio spesso la seguente struttura:

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

    ...

    else
         #default implementation
    end
end

Ciò consente all'utente dell'oggetto di utilizzare il nome_metodo pulito e chiaro: metodo Ma se vuole ottimizzare l'esecuzione, può chiamare direttamente il metodo corretto.

Inoltre, rende i test più chiari e migliori.


1

ci sono già ottime risposte sul perché lato della domanda. tuttavia, se qualcuno è alla ricerca di altre soluzioni , controlla la gemma funzionale-rubino che si ispira alle caratteristiche di corrispondenza del motivo elisir .

 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!"   
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.