Передача методу як параметра в Ruby


117

Я намагаюся трохи повозитися з Рубі. Для цього я намагаюся реалізувати алгоритми (наведені в Python) з книги "Програмування колективного інтелекту" Рубі.

У главі 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)

Відповіді:


100

Ви хочете прок-об’єкт:

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

Але це здається, що ви хотіли б тут більше шматок багаторазового використання коду.


1
Я думаю, що другий варіант є найкращим варіантом (тобто, використовуючи Object.send ()), недоліком є ​​те, що вам потрібно використовувати клас для всього цього (як це все-таки слід робити в ОО :)). Це СУШЕ, ніж весь час передавати блок (Proc), і ви навіть можете передавати аргументи методом обгортки.
Джиммі Стенке

4
Як додаток, якщо ви хочете зробити foo.bar(a,b)з відправленням, це так foo.send(:bar, a, b). Оператор splat (*) дозволяє вам це зробити, foo.send(:bar, *[a,b])якщо ви виявите, що хочете мати довільний довгастий масив аргументів - якщо припустити, що метод бар може занурити їх
xxjjnn

99

Зауваження, що стосуються блоків і програм, правильні тим, що вони звичайніші в Ruby. Але ви можете передати метод, якщо хочете. Ви закликаєте methodотримати метод і .callвикликати його:

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

3
Це цікаво. Варто зазначити, що ви передзвонюєте method( :<name> )лише один раз під час перетворення імені свого методу в символ, що викликається. Ви можете зберегти цей результат у змінній чи параметрі та продовжувати передавати його дочірнім функціям, як і будь-яка інша змінна з цього часу ...

1
Або, можливо, замість того, щоб використовувати синтаксис методу у списку аргументів, ви можете використовувати його, викликаючи метод таким чином: Weighttedknn (data, vec1, k, method (: gaussian))
Yahya

1
Цей спосіб краще, ніж спілкуватися з процесом або блоком, оскільки вам не доведеться обробляти параметри - він просто працює з тим, що метод хоче.
danuker

3
Для завершення, якщо ви хочете передати метод, визначений десь в іншому місці, зробіть SomewhereElse.method(:method_name). Це дуже круто!
medik

Це може бути власне питання, але як я можу визначити, чи символ посилається на функцію чи щось інше? Я спробував, :func.classале це простоsymbol
тихий конкурс

46

Ви можете передати метод як параметр із method(:function)способом. Нижче дуже простий приклад:

def подвійний (a)
  повернути a * 2 
кінець
=> нуль

def method_with_function_as_param (зворотний виклик, номер) 
  callback.call (номер) 
кінець 
=> нуль

method_with_function_as_param (метод (: подвійний), 10) 
=> 20

7
Я зіткнувся з проблемою для методу зі складнішою сферою застосування, і нарешті з’ясував, як це зробити, сподіваюся, це може комусь допомогти: Якщо ваш метод є, наприклад, в іншому класі, вам слід назвати останній рядок коду, як, method_with_function_as_param(Class.method(:method_name),...)а ніmethod(:Class.method_name)
V Déhaye

Завдяки вашій відповіді я виявив метод, який називається method. Зробив свій день, але, мабуть, тому я віддаю перевагу функціональним мовам, не потрібно робити таких акробатик, щоб отримати те, що ти хочеш. У всякому разі, я копаю рубін
Людовик Куті

25

Нормальний спосіб Ruby зробити це - використовувати блок.

Так це було б щось на зразок:

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

І використовується як:

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

Ця модель широко використовується в Ruby.



1

Ви повинні викликати метод "виклик" об'єкта функції:

weight = weightf.call( dist )

EDIT: як пояснено в коментарях, такий підхід є неправильним. Це буде працювати, якщо ви використовуєте Програми замість звичайних функцій.


1
Коли він перебуває weightf = gaussianу списку аргументів, він насправді намагається виконати gaussianі призначити результат як значення за замовчуванням weightf. У виклику немає необхідних аргументів та збоїв. Таким чином, weightf - це навіть не об'єкт Proc з методом виклику.
Алекс Вейн

1
Це (тобто роблю це неправильно та коментар пояснює чому) насправді дозволило мені повністю зрозуміти прийняту відповідь, тож дякую! +1
rmcsharry

1

Я рекомендую використовувати ampersand, щоб мати доступ до названих блоків у межах функції. Дотримуючись рекомендацій, наведених у цій статті, ви можете написати щось подібне (це справжній брухт від моєї робочої програми):

  # 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.


-5

ви також можете використовувати "eval" і передавати метод як аргумент рядка, а потім просто оцінювати його в іншому методі.


1
Це дійсно погана практика, ніколи цього не робіть!
Розробник

@Developer чому це вважається поганою практикою?
jlesse

Серед причин продуктивності evalможе виконувати довільний код, тому він надзвичайно вразливий до різних атак.
розробник
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.