2009-02-07 17 views
86

Próbuję trochę pomieszać z Ruby. Dlatego próbuję implementować algorytmy (podane w Pythonie) z książki "Programowanie Kolektywnej Inteligencji" Ruby.Przekazywanie metody jako parametru w Ruby

W rozdziale 8 autor podaje metodę a jako parametr. To wydaje się działać w Pythonie, ale nie w Ruby.

Mam tu metoda

def gaussian(dist, sigma=10.0) 
    foo 
end 

i chcesz się połączyć to z innej metody

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

Wszystko co mam to błąd

ArgumentError: wrong number of arguments (0 for 1) 

Odpowiedz

71

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

Wystarczy pamiętać, że nie można ustawić domyślny argument w deklaracji bloku takiego. Musisz więc użyć splat i ustawić domyślną wartość w samym kodzie proc.


Lub, w zależności od zakresu tego wszystkiego, może być łatwiej podać zamiast nazwy 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ć kompletny fragment kodu. W zależności od tego, jak zorganizować to może trzeba wymienić self.send z object_that_has_the_these_math_methods.send


Last but not least, można powiesić zablokować metody.

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 

Wygląda na to, że chciałbyś otrzymać więcej fragmentów kodu wielokrotnego użytku.

+0

Myślę, że druga opcja jest najlepszą opcją (to znaczy, używając Object.send()), wadą jest to, że musisz użyć klasy dla wszystkich (tak powinieneś zrobić w OO i tak :)). Jest bardziej SUCHA niż przejście bloku (Proc) przez cały czas, a nawet możesz przekazywać argumenty za pomocą metody wrappera. –

+2

Jako dodatek, jeśli chcesz wykonać 'foo.bar (a, b)' przy wysyłaniu, jest to 'foo.send (: bar, a, b)'. Operator splat (*) pozwala ci na zrobienie 'foo.send (: bar, * [a, b])' jeśli znajdziesz żądaną arbitralną tablicę argumentów - zakładając, że metoda słupkowa może je pochłonąć – xxjjnn

0

Trzeba wywołać metodę "wywołanie" obiektu funkcji:

weight = weightf.call(dist) 

EDYCJA: jak wyjaśniono w komentarzach, podejście to jest niewłaściwe. Będzie działał, jeśli używasz Proc, zamiast normalnych funkcji.

+0

Kiedy robi "weightf = gaussian' na liście arg, to faktycznie próbuje wykonać' gaussian' i przypisać wynik jako domyślną wartość weightf. Wywołanie nie wymaga args i awarii. A więc weightf nie jest jeszcze obiektem proc z metodą call. –

+0

Och, rozumiem. Dzięki za poprawienie mnie =) – Tiago

22

Normalnym sposobem Ruby jest użycie bloku.

Więc byłoby 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 wzór jest szeroko stosowane w Ruby.

81

Komentarze odnoszące się do bloków i Procs są poprawne, ponieważ są bardziej typowe w Ruby. Ale możesz przekazać metodę, jeśli chcesz.Nazywasz method dostać się sposobu i .call to nazwać:

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

jest interesujący. Warto zauważyć, że nazywacie 'method (: )' tylko raz podczas konwersji nazwy metody na symbol wywoływalny. Możesz zapisać ten wynik w zmiennej lub parametrze i przechodzić dalej do funkcji potomnych, jak każda inna zmienna od tego momentu ... – nus

+1

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

+1

Ta metoda jest lepsza niż mucking z proc lub bloku, ponieważ nie musisz obsługiwać parametrów - po prostu działa z czymkolwiek metoda chce. – danuker

29

można przekazać metodę jako parametr z method(:function) sposób. Poniżej prosty przykład:

 
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

Mam do czynienia z problemem dla metody o bardziej skomplikowanym zakresie, i wreszcie zorientowałem się, jak to zrobić, mam nadzieję, że to może pomóc komuś: Jeśli twoja metoda jest na przykład w innej klasie, powinieneś wywołaj ostatni wiersz kodu, taki jak 'method_with_function_as_param (Class.method (: nazwa_metody), ...)' a nie 'method (: Class.method_name)' –

0

Polecam używanie ampersand, aby uzyskać dostęp do nazwanych bloków w ramach funkcji. Zgodnie z zaleceniami podanymi w this article można napisać coś takiego (to jest prawdziwy złom 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żna wywołać tę funkcję tak:

@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 

Tyle o mocy bloków rubinu.

-5

można również skorzystać z "eval" i przekazać metodę jako argument łańcuch, a następnie po prostu eval go w inny sposób.

+0

To jest naprawdę zła praktyka, nigdy tego nie rób! – Developer

Powiązane problemy