Передача метода в качестве параметра в Ruby - PullRequest
105 голосов
/ 07 февраля 2009

Я пытаюсь немного повозиться с Руби. Поэтому я пытаюсь реализовать алгоритмы (приведенные в 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)

Ответы [ 8 ]

92 голосов
/ 07 февраля 2009

Комментарии, относящиеся к блокам и процессам, верны в том смысле, что они более обычны в Ruby. Но вы можете передать метод, если хотите. Вы вызываете method, чтобы получить метод, и .call, чтобы вызвать его:

def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
  ...
  weight = weightf.call( dist )
  ...
end
88 голосов
/ 07 февраля 2009

Вы хотите объект 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

Просто обратите внимание, что вы не можете установить аргумент по умолчанию в объявлении блока, как это. Поэтому вам нужно использовать сплат и установить настройки по умолчанию в самом коде процесса.


Или, в зависимости от объема всего этого, вместо этого может быть проще передать имя метода.

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

Но звучит так, как будто вы хотели бы больше кусков кода, которые можно использовать повторно.

39 голосов
/ 04 ноября 2010

Вы можете передать метод в качестве параметра с 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
24 голосов
/ 07 февраля 2009

Обычный способ сделать это в Ruby - использовать блок.

Так было бы что-то вроде:

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

И используется как:

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

Этот шаблон широко используется в Ruby.

12 голосов
/ 30 декабря 2013

Вы можете использовать оператор & в экземпляре 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

0 голосов
/ 18 марта 2015

Вы также можете использовать «eval» и передать метод в качестве строкового аргумента, а затем просто проверить его в другом методе.

0 голосов
/ 31 мая 2013

Я бы рекомендовал использовать амперсанд, чтобы иметь доступ к именованным блокам внутри функции. Следуя рекомендациям, приведенным в этой статье , вы можете написать что-то вроде этого (это настоящий кусок моей рабочей программы):

  # 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, вы можете вызвать эту функцию следующим образом:

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

Если вам не нужно фильтровать ваш выбор, вы просто опускаете блок:

@categories = make_select_list( Category ) # selects all categories

Так много для мощности блоков Ruby.

0 голосов
/ 07 февраля 2009

Вы должны вызвать метод «вызов» объекта функции:

weight = weightf.call( dist )

РЕДАКТИРОВАТЬ: как объяснено в комментариях, этот подход является неправильным. Это будет работать, если вы используете Procs вместо обычных функций.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...