web-dev-qa-db-fra.com

Passer une méthode en tant que paramètre dans Ruby

J'essaie de déconner un peu avec Ruby. Par conséquent, j'essaie d'implémenter les algorithmes (donnés en Python) du livre "Programming Collective Intelligence" de Ruby.

Au chapitre 8, l'auteur passe une méthode a en tant que paramètre. Cela semble fonctionner dans Python mais pas dans Ruby.

J'ai ici la méthode

def gaussian(dist, sigma=10.0)
  foo
end

et veulent appeler cela avec une autre méthode

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

Tout ce que j'ai c'est une erreur

ArgumentError: wrong number of arguments (0 for 1)
107

Vous voulez un objet 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

Notez simplement que vous ne pouvez pas définir un argument par défaut dans une déclaration de bloc comme celle-ci. Vous devez donc utiliser un splat et configurer la valeur par défaut dans le code proc lui-même.


Ou, selon votre portée, il peut être plus facile de passer un nom de méthode à la place.

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

Dans ce cas, vous appelez simplement une méthode définie sur un objet plutôt que de transmettre un bloc de code complet. En fonction de votre structure, vous devrez peut-être remplacer self.send avec object_that_has_the_these_math_methods.send


Dernier point mais non le moindre, vous pouvez suspendre un bloc de la méthode.

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

Mais il semblerait que vous souhaitiez davantage de morceaux de code réutilisables ici.

91
Alex Wayne

Les commentaires faisant référence aux blocs et Procs sont corrects en ce qu'ils sont plus habituels en Ruby. Mais vous pouvez passer une méthode si vous voulez. Vous appelez method pour obtenir la méthode et .call pour l'appeler:

def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
  ...
  weight = weightf.call( dist )
  ...
end
94
Daniel Lucraft

Vous pouvez passer une méthode en paramètre avec method(:function) way. Voici un exemple très simple:

 def double (a) 
 renvoyer a * 2 
 end 
 => nil 
 
 def method_with_function_as_param (rappel, numéro) 
 callback.call (number) 
 end 
 => nil 
 
 method_with_function_as_param (method (: double), 10) 
 => 20 
40
Patrick Wong

La méthode normale Ruby) consiste à utiliser un bloc.

Donc, ce serait quelque chose comme:

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

Et utilisé comme:

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

Ce modèle est largement utilisé en Ruby.

24
Chuck

Vous pouvez utiliser le & opérateur sur l’instance Method de votre méthode pour convertir la méthode en bloc .

Exemple:

def foo(arg)
  p arg
end

def bar(&block)
  p 'bar'
  block.call('foo')
end

bar(&method(:foo))

Plus de détails sur http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

13

Je recommanderais d'utiliser esperluette pour avoir un accès aux blocs nommés dans une fonction. En suivant les recommandations données dans cet article , vous pouvez écrire quelque chose comme ceci (ceci est un véritable extrait de mon programme de travail):

  # 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

Par la suite, vous pouvez appeler cette fonction comme suit:

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

Si vous n'avez pas besoin de filtrer votre sélection, vous omettez simplement le bloc:

@categories = make_select_list( Category ) # selects all categories

Voilà pour le pouvoir de Ruby bloque.

1
vitrums

Vous devez appeler la méthode "call" de l'objet function:

weight = weightf.call( dist )

EDIT: comme expliqué dans les commentaires, cette approche est fausse. Cela fonctionnerait si vous utilisez Procs au lieu de fonctions normales.

1
Tiago