2009-02-07 72 views
86

Estoy tratando de perder el tiempo con Ruby. Por lo tanto, intento implementar los algoritmos (dados en Python) del libro "Programming Collective Intelligence" Ruby.Pasar un método como parámetro en Ruby

En el capítulo 8, el autor pasa un método a como parámetro. Esto parece funcionar en Python pero no en Ruby.

Tengo aquí el método

def gaussian(dist, sigma=10.0) 
    foo 
end 

y quieren llamar a esto con otro método

def weightedknn(data, vec1, k = 5, weightf = gaussian) 
    foo 
    weight = weightf(dist) 
    foo 
end 

Todo lo que tengo es un error

ArgumentError: wrong number of arguments (0 for 1) 

Respuesta

71

desea que un objeto proc:

gaussian = Proc.new do |dist, *args| 
    sigma = args.first || 10.0 
    ... 
end 

def weightedknn(data, vec1, k = 5, weightf = gaussian) 
    ... 
    weight = weightf.call(dist) 
    ... 
end 

Ten en cuenta que no se puede establecer un argumento predeterminado en una declaración el bloque de esa manera. Entonces necesita usar un splat y configurar el predeterminado en el código de proc mismo.


O, dependiendo del alcance de todo esto, puede ser más fácil pasar el nombre de un método en su lugar.

def weightedknn(data, vec1, k = 5, weightf = :gaussian) 
    ... 
    weight = self.send(weightf) 
    ... 
end 

En este caso son sólo llamar a un método que se define en un objeto en lugar de pasar en un trozo de código completa. Dependiendo de cómo se estructura este puede ser necesario reemplazar con self.sendobject_that_has_the_these_math_methods.send


Por último, pero no menos importante, se puede colgar un bloque fuera del método.

def weightedknn(data, vec1, k = 5) 
    ... 
    weight = 
    if block_given? 
     yield(dist) 
    else 
     gaussian.call(dist) 
    end 
    end 
    ... 
end 

weightedknn(foo, bar) do |dist| 
    # square the dist 
    dist * dist 
end 

Pero parece que le gustaría obtener más fragmentos de código reutilizables aquí.

+0

Creo que la segunda opción es la mejor opción (es decir, usar Object.send()), el inconveniente es que necesitas usar una clase para todo (que es como debes hacerlo en OO de todos modos :)). Es más SECO que pasar un bloque (Proc) todo el tiempo, e incluso se pueden pasar argumentos a través del método de envoltura. –

+2

Como una adición, si desea hacer 'foo.bar (a, b)' con send, es 'foo.send (: bar, a, b)'. El operador splat (*) le permite hacer 'foo.send (: bar, * [a, b])' en caso de que desee tener una matriz de argumentos arbitrariamente alargada, suponiendo que el método de la barra pueda absorberlos – xxjjnn

0

Tienes que llamar al método "llamada" del objeto de función:

weight = weightf.call(dist) 

EDITAR: como se explica en los comentarios, este enfoque es incorrecto. Funcionaría si está utilizando Procs en lugar de funciones normales.

+0

Cuando hace 'weightf = gaussian' en la lista arg, en realidad está tratando de ejecutar' gaussian' y asigna el resultado como el valor predeterminado de weightf. La llamada no ha requerido args y bloqueos. Así que weightf ni siquiera es un objeto de proceso con un método de llamada por el momento. –

+0

Oh, lo tengo. Gracias por corregirme =) – Tiago

22

La forma normal de Ruby para hacer esto es usar un bloque.

Por lo tanto, sería algo así como:

def weightedknn(data, vec1, k = 5) 
    foo 
    weight = yield(dist) 
    foo 
end 

y usados ​​como:

weightenknn(data, vec1) { |dist| gaussian(dist) } 

Este patrón se utiliza ampliamente en Ruby.

81

Los comentarios referentes a bloques y Procs son correctos porque son más comunes en Ruby. Pero puede pasar un método si lo desea.Se llama a method para obtener el método y .call llamarlo:

def weightedknn(data, vec1, k = 5, weightf = method(:gaussian)) 
    ... 
    weight = weightf.call(dist) 
    ... 
end 
+1

This es interesante. Vale la pena señalar que llama al 'método (: )' solo una vez al convertir el nombre de su método en un símbolo invocable. Puede almacenar ese resultado en una variable o un parámetro y seguir pasándolo a las funciones secundarias como cualquier otra variable a partir de ese momento ... – nus

+1

O tal vez, en lugar de usar la sintaxis _method_ en la lista de argumentos, puede usarlo mientras invoca el método de la siguiente manera: weightedknn (data, vec1, k, method (: gaussian)) – Yahya

+1

Este método es mejor que usar un programa o bloque, ya que no tiene que manejar parámetros, simplemente funciona con cualquier método quiere. – danuker

29

se puede pasar un método como parámetro con method(:function) manera. A continuación un ejemplo muy simple:

 
def double(a) 
    return a * 2 
end 
=> nil 

def method_with_function_as_param(callback, number) 
    callback.call(number) 
end 
=> nil 

method_with_function_as_param(method(:double) , 10) 
=> 20 
+2

Me enfrenté a un problema para un método con un alcance más complicado, y finalmente descubrí cómo hacerlo, espero que esto pueda ayudar a alguien: Si su método es, por ejemplo, en otra clase, debe llame a la última línea de código como 'method_with_function_as_param (Class.method (: method_name), ...)' y not 'method (: Class.method_name)' –

0

Recomendaría usar el signo & para tener acceso a los bloques con nombre dentro de una función. Siguiendo las recomendaciones dadas en this article se puede escribir algo como esto (esto es un verdadero pedazo de mi programa de trabajo):

# Returns a valid hash for html form select element, combined of all entities 
    # for the given +model+, where only id and name attributes are taken as 
    # values and keys correspondingly. Provide block returning boolean if you 
    # need to select only specific entities. 
    # 
    # * *Args* : 
    # - +model+ -> ORM interface for specific entities' 
    # - +&cond+ -> block {|x| boolean}, filtering entities upon iterations 
    # * *Returns* : 
    # - hash of {entity.id => entity.name} 
    # 
    def make_select_list(model, &cond) 
    cond ||= proc { true } # cond defaults to proc { true } 
    # Entities filtered by cond, followed by filtration by (id, name) 
    model.all.map do |x| 
     cond.(x) ? { x.id => x.name } : {} 
    end.reduce Hash.new do |memo, e| memo.merge(e) end 
    end 

Afterwerds, puede llamar a esta función como esta:

@contests = make_select_list Contest do |contest| 
    logged_admin? or contest.organizer == @current_user 
end 

Si no es necesario filtrar su selección, simplemente omite el bloque:

@categories = make_select_list(Category) # selects all categories 

Esto en cuanto a la potencia de ruby ​​bloques.

-5

también se puede utilizar "eval", y aprobar el método como un argumento de cadena, y luego simplemente Eval en el otro método.

+0

Esto es realmente una mala práctica, ¡nunca lo haga! – Developer

Cuestiones relacionadas