¿Por qué Ruby no soporta la sobrecarga del método?

146

En lugar de soportar la sobrecarga de métodos, Ruby sobrescribe los métodos existentes. ¿Alguien puede explicar por qué el lenguaje fue diseñado de esta manera?

Rambo
fuente

Respuestas:

166

La sobrecarga de métodos se puede lograr declarando dos métodos con el mismo nombre y firmas diferentes. Estas firmas diferentes pueden ser:

  1. Argumentos con diferentes tipos de datos, por ejemplo: method(int a, int b) vs method(String a, String b)
  2. Número variable de argumentos, por ejemplo: method(a) vs method(a, b)

No podemos lograr la sobrecarga de métodos usando la primera manera porque no hay una declaración de tipo de datos en ruby ​​( lenguaje de tipo dinámico ). Entonces, la única forma de definir el método anterior esdef(a,b)

Con la segunda opción, podría parecer que podemos lograr la sobrecarga del método, pero no podemos. Digamos que tengo dos métodos con diferentes números 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.

Por lo tanto, Ruby necesita mantener un método en la cadena de búsqueda de métodos con un nombre único.

nkm
fuente
22
La respuesta de @ Jörg W Mittag, enterrada muy por debajo, definitivamente vale la pena leerla.
user2398029
1
Y la respuesta de @Derek Ekins, enterrada aún más, ofrece una alternativa
Cyril Duchon-Doris el
Nota para futuros rubíes ... FWIW puede hacer esto con contratos joya egonschiele.github.io/contracts.ruby/#method-overloading
engineerDave
214

"Sobrecarga" es un término que simplemente no tiene sentido en Ruby. Básicamente es un sinónimo de "despacho estático basado en argumentos", pero Ruby no tiene despacho estático en absoluto . Entonces, la razón por la cual Ruby no admite el envío estático basado en los argumentos, es porque no admite el envío estático, punto. No admite el envío estático de ningún tipo , ya sea basado en argumentos o de otra manera.

Ahora, si no está preguntando específicamente sobre la sobrecarga, sino quizás sobre el despacho dinámico basado en argumentos, entonces la respuesta es: porque Matz no lo implementó. Porque nadie más se molestó en proponerlo. Porque nadie más se molestó en implementarlo.

En general, el despacho dinámico basado en argumentos en un lenguaje con argumentos opcionales y listas de argumentos de longitud variable, es muy difícil de acertar, y aún más difícil de mantener comprensible. Incluso en lenguajes con despacho estático basado en argumentos y sin argumentos opcionales (como Java, por ejemplo), a veces es casi imposible decir para un simple mortal, qué sobrecarga se va a elegir.

En C #, puede codificar cualquier problema de 3-SAT en resolución de sobrecarga, lo que significa que la resolución de sobrecarga en C # es NP-hard.

Ahora intente eso con el despacho dinámico , donde tiene la dimensión de tiempo adicional para mantener en su cabeza.

Hay lenguajes que se distribuyen dinámicamente en función de todos los argumentos de un procedimiento, a diferencia de los lenguajes orientados a objetos, que solo se distribuyen en el selfargumento zeroth "oculto" . Common Lisp, por ejemplo, distribuye los tipos dinámicos e incluso los valores dinámicos de todos los argumentos. Clojure se despacha en una función arbitraria de todos los argumentos (que por cierto es extremadamente genial y extremadamente poderoso).

Pero no conozco ningún lenguaje OO con envío dinámico basado en argumentos. Martin Odersky dijo que podría considerar agregar un despacho basado en argumentos a Scala, pero solo si puede eliminar la sobrecarga al mismo tiempo y ser compatible con versiones anteriores tanto con el código Scala existente que usa sobrecarga como con Java (mencionó especialmente Swing y AWT que juegan algunos trucos extremadamente complejos que ejercitan prácticamente todos los casos desagradables de esquina oscura de las reglas de sobrecarga bastante complejas de Java). He tenido algunas ideas sobre cómo agregar un despacho basado en argumentos a Ruby, pero nunca pude descubrir cómo hacerlo de una manera compatible con versiones anteriores.

Jörg W Mittag
fuente
55
Esta es la respuesta correcta. La respuesta aceptada simplifica demasiado. C # TIENE parámetros con nombre y parámetros opcionales y todavía implementa la sobrecarga, por lo que no es tan simple como " def method(a, b = true)no funcionará, por lo tanto, la sobrecarga de métodos es imposible". No es; Solo es difícil. Sin embargo, encontré ESTA respuesta realmente informativa.
tandrewnichols
1
@tandrewnichols: solo para dar una idea de cuán "difícil" es la resolución de sobrecarga en C # ... es posible codificar cualquier problema de 3-SAT como resolución de sobrecarga en C # y hacer que el compilador lo resuelva en tiempo de compilación, haciendo así la resolución de sobrecarga en C # NP -hard (se sabe que 3-SAT es NP-completo). Ahora imagine tener que hacer eso no una vez por sitio de llamada en tiempo de compilación, sino una vez por llamada de método para cada llamada de método en tiempo de ejecución.
Jörg W Mittag
1
@ JörgWMittag ¿Podría incluir un enlace que muestre una codificación de un problema 3-SAT en el mecanismo de resolución de sobrecarga?
Calamarly
2
No parece que "sobrecargar" sea sinónimo de "despacho basado en argumentos estáticos". El despacho estático basado en argumentos es simplemente la implementación más común de sobrecarga. La sobrecarga es un término agnóstico de implementación que significa "mismo nombre de método pero diferentes implementaciones en el mismo alcance".
snovity
85

Supongo que está buscando la capacidad de hacer esto:

def my_method(arg1)
..
end

def my_method(arg1, arg2)
..
end

Ruby apoya esto de una manera diferente:

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

Un patrón común también es pasar las opciones como un 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 ayude

Derek Ekins
fuente
15
+1 por proporcionar lo que muchos de nosotros solo queremos saber: cómo trabajar con un número variable de argumentos en los métodos de Ruby.
cenizas999
3
Esta respuesta podría beneficiarse de información adicional sobre argumentos opcionales. (Y quizás también llamado argumentos, ya que esos son una cosa.)
Ajedi32
9

La sobrecarga de métodos tiene sentido en un lenguaje con tipeo estático, donde puede distinguir entre diferentes tipos de argumentos

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

así como entre diferentes números de argumentos

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

La primera distinción no existe en rubí. Ruby usa tipeo dinámico o "tipeo de pato". La segunda distinción puede manejarse mediante argumentos predeterminados o trabajando con argumentos:

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


def f(*args)
  case args.size
  when  
     ...
  when 2
    ...
  when 3
    ...
  end
end
bjelli
fuente
Esto no tiene nada que ver con escribir fuerte. Ruby está fuertemente escrito, después de todo.
Jörg W Mittag
8

Esto no responde a la pregunta de por qué Ruby no tiene una sobrecarga de métodos, pero las bibliotecas de terceros pueden proporcionarla.

La biblioteca contratos.ruby permite la sobrecarga. Ejemplo adaptado del 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

Tenga en cuenta que esto es en realidad más poderoso que la sobrecarga de Java, porque puede especificar valores para que coincidan (por ejemplo 1), no simplemente tipos.

Sin embargo, verá un rendimiento reducido al usar esto; Tendrá que ejecutar puntos de referencia para decidir cuánto puede tolerar.

Kelvin
fuente
1
En las aplicaciones del mundo real con cualquier tipo de IO, tendrá una desaceleración de solo 0.1-10% (dependiendo de qué tipo de IO).
Waterlink
1

A menudo hago la siguiente estructura:

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

    ...

    else
         #default implementation
    end
end

Esto permite al usuario del objeto usar el método nombre_método limpio y claro: método Pero si desea optimizar la ejecución, puede llamar directamente al método correcto.

Además, hace que su prueba sea más clara y mejor.

Represa
fuente
1

Ya hay excelentes respuestas sobre por qué lado de la pregunta. sin embargo, si alguien busca otras soluciones, busque la gema funcional de rubí inspirada en las características de coincidencia de patrones de 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
fuente