Przekazywanie metody jako parametru w Rubim


117

Próbuję trochę pomieszać z Rubim. Dlatego staram się zaimplementować algorytmy (podane w Pythonie) z książki „Programming Collective Intelligence” Ruby.

W rozdziale 8 autor przekazuje metodę a jako parametr. Wydaje się, że działa to w Pythonie, ale nie w Rubim.

Mam tutaj metodę

def gaussian(dist, sigma=10.0)
  foo
end

i chcę to wywołać inną metodą

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

Mam tylko błąd

ArgumentError: wrong number of arguments (0 for 1)

Odpowiedzi:


100

Chcesz obiekt 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

Zwróć uwagę, że nie możesz ustawić domyślnego argumentu w takiej deklaracji bloku. Musisz więc użyć ikony i ustawić wartość domyślną w samym kodzie proc.


Lub, w zależności od zakresu tego wszystkiego, łatwiej będzie zamiast tego przekazać nazwę metody.

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

W tym przypadku po prostu wywołujesz metodę, która jest zdefiniowana na obiekcie, zamiast przekazywać cały fragment kodu. W zależności od tego, jak zorganizować to może trzeba wymienić self.sendzobject_that_has_the_these_math_methods.send


Wreszcie, możesz zawiesić blok na metodzie.

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

Ale wygląda na to, że chciałbyś mieć więcej fragmentów kodu wielokrotnego użytku tutaj.


1
Myślę, że druga opcja jest najlepszą opcją (czyli używając Object.send ()), wadą jest to, że musisz użyć klasy do tego wszystkiego (co i tak powinieneś zrobić w OO :)). Jest bardziej SUCHA niż przekazywanie bloku (Proc) przez cały czas, a nawet można przekazywać argumenty za pomocą metody opakowującej.
Jimmy Stenke

4
Jako dodatek, jeśli chcesz zrobić foo.bar(a,b)z wysyłaniem, to jest foo.send(:bar, a, b). Operator splat (*) pozwala ci to zrobić foo.send(:bar, *[a,b]), jeśli chcesz mieć dowolną wydłużoną tablicę argumentów - zakładając, że metoda bar może je wchłonąć
xxjjnn

99

Komentarze odnoszące się do bloków i procesów są poprawne, ponieważ występują częściej w Rubim. Ale możesz przekazać metodę, jeśli chcesz. Wołasz, methodaby pobrać metodę i .callwywołać ją:

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

3
To jest interesujące. Warto zauważyć, że dzwonisz method( :<name> )tylko raz podczas konwersji nazwy metody na symbol możliwy do wywołania . Możesz zapisać ten wynik w zmiennej lub parametrze i dalej przekazywać go do funkcji potomnych jak każda inna zmienna od tej pory ...

1
A może zamiast używać składni metody na liście argumentów, możesz jej użyć podczas wywoływania metody w następujący sposób: weightedknn (dane, vec1, k, metoda (: gaussian))
Yahya

1
Ta metoda jest lepsza niż grzebanie w proc lub bloku, ponieważ nie musisz obsługiwać parametrów - po prostu działa z dowolną metodą.
danuker

3
Na zakończenie, jeśli chcesz przekazać metodę zdefiniowaną gdzie indziej, zrób SomewhereElse.method(:method_name). To fajnie!
medik

To może być własne pytanie, ale jak mogę określić, czy symbol odnosi się do funkcji, czy do czegoś innego? Próbowałem, :func.classale to tylkosymbol
cichy Konkurs

46

Możesz przekazać metodę jako parametr ze method(:function)sposobem. Poniżej bardzo prosty przykład:

def podwójna (a)
  powrót a * 2 
koniec
=> zero

def metoda_with_function_as_param (oddzwonienie, numer) 
  callback.call (numer) 
koniec 
=> zero

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

7
Napotkałem problem z metodą o bardziej skomplikowanym zakresie i w końcu wymyśliłem, jak to zrobić, mam nadzieję, że może to komuś pomóc: Jeśli twoja metoda jest na przykład w innej klasie, powinieneś wywołać ostatnią linię kodu jak method_with_function_as_param(Class.method(:method_name),...)a niemethod(:Class.method_name)
V , Déhaye

Dzięki Twojej odpowiedzi odkryłem metodę tzw method. Zrobiłem mój dzień, ale myślę, że dlatego wolę języki funkcjonalne, nie ma potrzeby robienia takich akrobacji, aby dostać to, czego chcesz. W każdym razie kopie rubin
Ludovic Kuty

25

Normalnym sposobem w Rubim jest użycie bloku.

Więc byłoby to coś takiego:

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

I używane jak:

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

Ten wzorzec jest szeroko stosowany w Rubim.



1

Musisz wywołać metodę „call” obiektu funkcji:

weight = weightf.call( dist )

EDYCJA: jak wyjaśniono w komentarzach, to podejście jest błędne. To zadziała, jeśli używasz Procs zamiast normalnych funkcji.


1
Kiedy robi to weightf = gaussianna liście argumentów, w rzeczywistości próbuje wykonać gaussiani przypisać wynik jako domyślną wartość weightf. Wywołanie nie wymaga argumentów ani awarii. Więc weightf nie jest jeszcze nawet obiektem proc z metodą call.
Alex Wayne,

1
To (tj. Robienie tego źle i komentarz wyjaśniający dlaczego) faktycznie pozwoliło mi w pełni zrozumieć zaakceptowaną odpowiedź, więc dzięki! +1
rmcsharry

1

Zalecałbym użycie znaku ampersand, aby mieć dostęp do nazwanych bloków w ramach funkcji. Postępując zgodnie z zaleceniami podanymi w tym artykule możesz napisać coś takiego (to jest prawdziwa notka z mojego programu roboczego):

  # 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, możesz wywołać tę funkcję w ten sposób:

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

Jeśli nie musisz filtrować swojego wyboru, po prostu pomiń blok:

@categories = make_select_list( Category ) # selects all categories

To tyle, jeśli chodzi o moc bloków Ruby.


-5

możesz także użyć "eval" i przekazać metodę jako argument typu string, a następnie po prostu evalować ją w innej metodzie.


1
To naprawdę zła praktyka, nigdy tego nie rób!
Deweloper

@Developer, dlaczego jest to uważane za złą praktykę?
jlesse

Spośród powodów związanych z wydajnością evalmoże wykonać dowolny kod, więc jest wyjątkowo podatny na różne ataki.
Deweloper
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.