Чи можете ви надати аргументи до синтаксису map (&: method) у Ruby?


116

Ви, напевно, знайомі з такою стенограмою Ruby ( aце масив):

a.map(&:method)

Наприклад, спробуйте наступне в irb:

>> a=[:a, 'a', 1, 1.0]
=> [:a, "a", 1, 1.0]
>> a.map(&:class)
=> [Symbol, String, Fixnum, Float]

Синтаксис a.map(&:class)- це скорочення для a.map {|x| x.class}.

Детальніше про цей синтаксис читайте у " Що означає карта (&: name) у Ruby? ".

Через синтаксис &:classви здійснюєте виклик методу classдля кожного елемента масиву.

Моє запитання: чи можете ви надати аргументи для виклику методу? А якщо так, то як?

Наприклад, як перетворити наступний синтаксис

a = [1,3,5,7,9]
a.map {|x| x + 2}

до &:синтаксису?

Я не припускаю, що &:синтаксис краще. Мене просто цікавить механіка використання &:синтаксису з аргументами.

Я припускаю, що ви знаєте, що +це метод класу Integer. Ви можете спробувати наступне в irb:

>> a=1
=> 1
>> a+(1)
=> 2
>> a.send(:+, 1)
=> 2

Відповіді:


139

Ви можете створити простий патч на Symbolтакому:

class Symbol
  def with(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

Що дозволить вам зробити не тільки це:

a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11] 

Але також багато інших цікавих речей, як-от передача кількох параметрів:

arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil] 
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil] 

І навіть працювати з inject, який передає два аргументи до блоку:

%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde" 

Або щось супер круто, як передача [стенограми] блоків до блоку скорочень:

[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"] 
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

Ось розмова, яку я провів із @ArupRakshit, пояснюючи це далі:
Чи можете ви надати аргументи до синтаксису map (&: method) у Ruby?


Як @amcaplan запропонував у коментарі нижче , ви можете створити коротший синтаксис, якщо перейменувати withметод на call. У цьому випадку рубін має вбудований ярлик для цього спеціального методу .().

Таким чином, ви можете використовувати вищезазначене так:

class Symbol
  def call(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11] 

[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]] 

5
Чудово, хотілося б, щоб це було частиною Ruby core!
Jikku Jose Jose

6
@UriAgassi Тільки тому, що багато бібліотек роблять це, це не робить гарною практикою. Хоча Symbol#withможе не існувати в основній бібліотеці, і визначення цього методу є менш руйнівним, ніж переосмислення існуючого методу, він все ще змінює (тобто перезаписує) реалізацію основного класу бібліотеки ruby. Практику потрібно робити дуже щадно і з великою обережністю. \ n \ n Будь ласка, подумайте про успадкування від існуючого класу та зміну новоствореного класу. Це, як правило, досягає порівнянних результатів без негативних побічних ефектів від зміни основних класів рубіну.
rudolph9

2
@ rudolph9 - прошу різнитися - визначення "перезаписати" полягає в тому, щоб писати над чимось, а це означає, що написаний код більше недоступний, і це явно не так. Щодо вашої пропозиції успадкувати Symbolклас - це не тривіально (якщо це можливо), оскільки це такий основний клас (наприклад, у нього немає newметоду), і його використання буде громіздким (якщо можливо), що переможе мета розширення ... якщо ви можете показати реалізацію, яка використовує це, і досягає порівнянних результатів - будь ласка, поділіться!
Урі Агассі

3
Мені подобається це рішення, але я думаю, що ви можете ще більше розважитися ним. Замість визначення withметоду визначте call. Тоді ви можете робити такі речі, як a.map(&:+.(2))з моменту object.()використання #callметоду. І поки ти на це, ти можеш писати цікаві речі на кшталт :+.(2).(3) #=> 5- відчуваєш себе LISPy, ні?
amcaplan

2
Я хотів би побачити це в основі - його загальну схему, яка може використовувати деякий цукор ala .map (&: foo)
Стівен

48

Для вашого прикладу можна зробити a.map(&2.method(:+)).

Arup-iMac:$ pry
[1] pry(main)> a = [1,3,5,7,9]
=> [1, 3, 5, 7, 9]
[2] pry(main)> a.map(&2.method(:+))
=> [3, 5, 7, 9, 11]
[3] pry(main)> 

Ось як це працює: -

[3] pry(main)> 2.method(:+)
=> #<Method: Fixnum#+>
[4] pry(main)> 2.method(:+).to_proc
=> #<Proc:0x000001030cb990 (lambda)>
[5] pry(main)> 2.method(:+).to_proc.call(1)
=> 3

2.method(:+)дає Methodоб’єкт. Потім &, 2.method(:+)власне, #to_procметод виклику , який робить його Procоб'єктом. Потім виконайте те, що ви називаєте оператором &: у Ruby? .


Розумне використання! Чи передбачає це припущення, що виклик методу можна застосовувати обома способами (наприклад, arr [елемент] .method (param) === param.method (arr [елемент])) чи я плутаюся?
Костас Русіс

@rkon Я також не отримав вашого запитання. Але якщо ви бачите Pryвиходи вище, ви можете отримати їх, як це працює.
Arup Rakshit

5
@rkon Це не працює обома способами. Він працює в цьому конкретному випадку, оскільки +є комутативним.
sawa

Як можна навести кілька аргументів? Як і в цьому випадку: a.map {| x | x.method (1,2,3)}
Зак Сю

1
це моя думка @sawa :) Це має сенс з +, але не для іншого методу чи скажімо, якщо ви хочете розділити кожне число на X.
Костас Русіс

11

Як підтверджується публікація, до якої ви посилаєтесь, a.map(&:class)це не скорочення, a.map {|x| x.class}а для a.map(&:class.to_proc).

Це означає, що to_procвикликається все, що слід &оператору.

Таким чином, ви можете дати це безпосередньо Procзамість цього:

a.map(&(Proc.new {|x| x+2}))

Я знаю, що, швидше за все, це перемагає мету вашого запитання, але я не бачу іншого способу навколо нього - це не те, що ви вказуєте, який метод викликати, ви просто передаєте йому те, що відповідає to_proc.


1
Також пам’ятайте, що ви можете встановити програми для локальних змінних і передати їх на карту. my_proc = Proc.new{|i| i + 1},[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9

10

Коротка відповідь: Ні.

Після відповіді @ rkon ви також можете це зробити:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]

9
Ви маєте рацію, але я не думаю, що &->(_){_ + 2}це коротше {|x| x + 2}.
sawa

1
Це не так, це говорить @rkon у своїй відповіді, тому я не повторював цього.
Агіс

2
@Аж, якщо ваша відповідь не коротша, вона виглядає краще.
Jikku Jose

1
Це дивовижне рішення.
BenMorganIO

5

Замість того, щоб самостійно виправляти основні класи, як у прийнятій відповіді, коротше і чіткіше використовувати функціональність самоцвету Facets :

require 'facets'
a = [1,3,5,7,9]
a.map &:+.(2)

5

Існує ще один власний варіант для перерахунків, який, на мою думку, є лише двома аргументами. клас Enumerableмає метод, with_objectякий потім повертає інший Enumerable.

Таким чином, ви можете викликати &оператора для методу з кожним елементом та об'єктом як аргументи.

Приклад:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+) # => [3, 5, 7, 9, 11]

Якщо ви хочете отримати більше аргументів, вам слід повторити процес, але, на мою думку, це некрасиво:

a = [1,3,5,7,9]
a.to_enum.with_object(2).map(&:+).to_enum.with_object(5).map(&:+) # => [8, 10, 12, 14, 16]

0

Я не впевнений у Symbol#withвже розміщеному, я досить спростив це, і він працює добре:

class Symbol
  def with(*args, &block)
    lambda { |object| object.public_send(self, *args, &block) }
  end
end

(також використовує public_sendзамість того, sendщоб запобігти виклику приватних методів, також callerвже використовується ruby, тому це було заплутано)

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