Можете ли вы предоставить аргументы синтаксису 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}.

Подробнее об этом синтаксисе читайте в разделе « Что означает map (&: 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] 

И даже work with 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. В этом случае в Ruby есть встроенный ярлык для этого специального метода .().

Таким образом, вы можете использовать это так:

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!
Jikku Jose 02
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 [element] .method (param) === param.method (arr [element])), или я запутался?
Костас Русис
@rkon Я тоже не понял твой вопрос. Но если вы видите Pryрезультаты выше, вы можете понять, как это работает.
Аруп Ракшит
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
Также имейте в виду, что вы можете установить procs для локальных переменных и передать их в map. my_proc = Proc.new{|i| i + 1},[1,2,3,4].map(&my_proc) => [2,3,4,5]
rudolph9 06
10

Короткий ответ: Нет.

Следуя ответу @ rkon, вы также можете сделать это:

a = [1,3,5,7,9]
a.map &->(_) { _ + 2 } # => [3, 5, 7, 9, 11]
Агис
источник
9
Вы правы, но я не думаю, что &->(_){_ + 2}короче {|x| x + 2}.
sawa
1
Это не то, что @rkon говорит в своем ответе, поэтому я не стал повторять.
Agis
2
@Agis, хотя твой ответ не короче, он выглядит лучше.
Jikku Jose
1
Это отличное решение.
BenMorganIO
6

Есть еще одна нативная опция для enumerables, которая, на мой взгляд, хороша только для двух аргументов. у класса 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]
Педро Аугусто
источник
5

Вместо того, чтобы самостоятельно исправлять базовые классы, как в принятом ответе, короче и чище использовать функциональность гема Facets :

require 'facets'
a = [1,3,5,7,9]
a.map &:+.(2)
michau
источник
0

Я не уверен в том, что Symbol#withуже было опубликовано, я немного упростил его, и он хорошо работает:

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

(также используется public_sendвместо того, sendчтобы предотвратить вызов частных методов, также callerуже используется Ruby, поэтому это сбивало с толку)

localhostdotdev
источник