Passaggio di un metodo come parametro in Ruby


117

Sto cercando di scherzare un po 'con Ruby. Per questo cerco di implementare gli algoritmi (forniti in Python) dal libro "Programming Collective Intelligence" Ruby.

Nel capitolo 8 l'autore passa un metodo a come parametro. Sembra funzionare in Python ma non in Ruby.

Ho qui il metodo

def gaussian(dist, sigma=10.0)
  foo
end

e vuoi chiamarlo con un altro metodo

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

Tutto quello che ho è un errore

ArgumentError: wrong number of arguments (0 for 1)

Risposte:


100

Vuoi un oggetto 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

Tieni presente che non puoi impostare un argomento predefinito in una dichiarazione di blocco come quella. Quindi è necessario utilizzare uno splat e impostare il valore predefinito nel codice proc stesso.


Oppure, a seconda della portata di tutto ciò, potrebbe essere più semplice passare invece un nome di metodo.

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

In questo caso stai solo chiamando un metodo definito su un oggetto invece di passare una porzione completa di codice. A seconda di come lo strutturi potresti dover sostituire self.sendconobject_that_has_the_these_math_methods.send


Ultimo ma non meno importante, puoi appendere un blocco al metodo.

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

Ma sembra che tu voglia più blocchi di codice riutilizzabili qui.


1
Penso che la seconda opzione sia l'opzione migliore (cioè, usando Object.send ()), lo svantaggio è che devi usare una classe per tutto questo (che è come dovresti fare comunque in OO :)). È più DRY che passare un blocco (Proc) tutto il tempo e potresti persino passare argomenti tramite il metodo wrapper.
Jimmy Stenke,

4
Inoltre, se vuoi fare foo.bar(a,b)con send, lo è foo.send(:bar, a, b). L'operatore splat (*) ti consente di fare foo.send(:bar, *[a,b])se dovessi scoprire di voler avere un array di argomenti di lunghezza arbitraria - supponendo che il metodo bar possa assorbirli
xxjjnn

99

I commenti che si riferiscono a blocchi e Proc sono corretti in quanto sono più usuali in Ruby. Ma puoi passare un metodo se vuoi. Chiami methodper ottenere il metodo e .callper chiamarlo:

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

3
Questo è interessante. Vale la pena notare che si chiama method( :<name> )solo una volta quando si converte il nome del metodo in un simbolo richiamabile. Puoi memorizzare quel risultato in una variabile o un parametro e continuare a passarlo alle funzioni figlio come qualsiasi altra variabile da quel momento in poi ...

1
O forse, invece di usare la sintassi del metodo nell'elenco degli argomenti, puoi usarla mentre invoca il metodo come segue: weightedknn (data, vec1, k, method (: gaussian))
Yahya

1
Questo metodo è meglio che andare in giro con un proc o un blocco, dal momento che non devi gestire i parametri: funziona solo con ciò che il metodo vuole.
danuker

3
Per il completamento, se vuoi passare un metodo definito altrove, fallo SomewhereElse.method(:method_name). È davvero fantastico!
medik

Questa potrebbe essere la sua stessa domanda, ma come posso determinare se un simbolo fa riferimento a una funzione o qualcos'altro? Ho provato :func.classma è solo unsymbol
tranquillo Concorso il

46

Puoi passare un metodo come parametro con method(:function)way. Di seguito è riportato un esempio molto semplice:

def double (a)
  restituire a * 2 
fine
=> zero

def method_with_function_as_param (callback, number) 
  callback.call (numero) 
fine 
=> zero

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

7
Ho affrontato un problema per un metodo con uno scopo più complicato e finalmente ho capito come farlo, spero che questo possa aiutare qualcuno: se il tuo metodo è ad esempio in un'altra classe, dovresti chiamare l'ultima riga di codice come method_with_function_as_param(Class.method(:method_name),...)e nonmethod(:Class.method_name)
V Déhaye

Grazie alla tua risposta, ho scoperto il metodo chiamato method. Ha reso la mia giornata, ma credo che sia per questo che preferisco i linguaggi funzionali, non c'è bisogno di fare tali acrobazie per ottenere quello che vuoi. Ad ogni modo, mi piace il rubino
Ludovic Kuty

25

Il modo normale per fare ciò in Ruby è usare un blocco.

Quindi sarebbe qualcosa del tipo:

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

E usato come:

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

Questo modello è ampiamente utilizzato in Ruby.



1

Devi chiamare il metodo "call" dell'oggetto funzione:

weight = weightf.call( dist )

EDIT: come spiegato nei commenti, questo approccio è sbagliato. Funzionerebbe se stai usando Procs invece delle normali funzioni.


1
Quando lo fa weightf = gaussiannell'elenco degli argomenti, sta effettivamente cercando di eseguire gaussiane assegnare il risultato come valore predefinito di weightf. La chiamata non ha richiesto argomenti e arresti anomali. Quindi weightf non è ancora nemmeno un oggetto proc con un metodo di chiamata.
Alex Wayne,

1
Questo (es. Farlo male e il commento che spiega perché) in realtà mi ha permesso di comprendere appieno la risposta accettata, quindi grazie! +1
rmcsharry

1

Consiglierei di usare la e commerciale per avere accesso ai blocchi denominati all'interno di una funzione. Seguendo i consigli forniti in questo articolo puoi scrivere qualcosa del genere (questo è un vero frammento del mio programma di lavoro):

  # 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, puoi chiamare questa funzione in questo modo:

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

Se non è necessario filtrare la selezione, ometti semplicemente il blocco:

@categories = make_select_list( Category ) # selects all categories

Questo per quanto riguarda la potenza dei blocchi Ruby.


-5

puoi anche usare "eval" e passare il metodo come argomento stringa, quindi semplicemente eval nell'altro metodo.


1
Questa è davvero una cattiva pratica, non farlo mai!
Sviluppatore

@Developer perché è considerata una cattiva pratica?
jlesse

Tra i motivi di prestazioni, evalpuò eseguire codice arbitrario, quindi è estremamente vulnerabile a vari attacchi.
Sviluppatore
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.