Передача метода в качестве параметра в Ruby
Я пытаюсь немного разобраться с Ruby. Поэтому я пытаюсь реализовать алгоритмы (приведенные в Python) из книги "Программирование коллективного интеллекта" Ruby.
В главе 8 автор передает метод a как параметр. Это похоже на работу в Python, но не в Ruby.
У меня есть метод
def gaussian(dist, sigma=10.0)
foo
end
и хотите вызвать это другим способом.
def weightedknn(data, vec1, k = 5, weightf = gaussian)
foo
weight = weightf(dist)
foo
end
Все, что у меня есть, это ошибка
ArgumentError: wrong number of arguments (0 for 1)
Ответы
Ответ 1
Вы хотите объект 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
Просто помните, что вы не можете установить аргумент по умолчанию в объявлении блока. Поэтому вам нужно использовать splat и установить значение по умолчанию в самом коде proc.
Или, в зависимости от вашей сферы действия, это может быть проще передать вместо имени метода.
def weightedknn(data, vec1, k = 5, weightf = :gaussian)
...
weight = self.send(weightf)
...
end
В этом случае вы просто вызываете метод, который определен на объекте, а не передаете полный фрагмент кода. В зависимости от того, как вы это структурируете, вам может потребоваться заменить self.send
на object_that_has_the_these_math_methods.send
И последнее, но не менее важное: вы можете повесить блок с помощью метода.
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
Но похоже, что здесь вы хотели бы использовать многократные фрагменты кода.
Ответ 2
Комментарии, относящиеся к блокам и Procs, верны в том, что они более обычны в Ruby. Но вы можете передать метод, если хотите. Вы вызываете method
, чтобы получить метод и .call
, чтобы вызвать его:
def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
...
weight = weightf.call( dist )
...
end
Ответ 3
Вы можете передать метод в качестве параметра с method(:function)
way. Ниже приведен очень простой пример:
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
Ответ 4
Обычный способ Ruby сделать это - использовать блок.
Итак, это будет что-то вроде:
def weightedknn( data, vec1, k = 5 )
foo
weight = yield( dist )
foo
end
И используется как:
weightenknn( data, vec1 ) { |dist| gaussian( dist ) }
Этот шаблон широко используется в Ruby.
Ответ 5
Вы можете использовать оператор &
в экземпляре Method
вашего метода для преобразования метода в блок.
Пример:
def foo(arg)
p arg
end
def bar(&block)
p 'bar'
block.call('foo')
end
bar(&method(:foo))
Подробнее на http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html
Ответ 6
Вы должны вызвать метод "вызов" объекта функции:
weight = weightf.call( dist )
EDIT: как поясняется в комментариях, этот подход неверен. Это будет работать, если вы используете Procs вместо обычных функций.
Ответ 7
Я бы рекомендовал использовать амперсанд для доступа к именованным блокам внутри функции. Следуя рекомендациям, приведенным в этой статье, вы можете написать что-то вроде этого (это настоящий отход от моей рабочей программы):
# 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
После этого вы можете вызвать эту функцию следующим образом:
@contests = make_select_list Contest do |contest|
logged_admin? or contest.organizer == @current_user
end
Если вам не нужно фильтровать свой выбор, вы просто опустите блок:
@categories = make_select_list( Category ) # selects all categories
Так много для мощности блоков Ruby.
Ответ 8
вы также можете использовать "eval" и передать метод в виде строкового аргумента, а затем просто оценить его в другом методе.