Можете ли вы предоставить аргументы синтаксису карты (&: метод) в Ruby?

Вероятно, вы знакомы со следующим сокращением 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

Ответ 1

Вы можете создать простой патч на 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, объясняющим его:
Можете ли вы предоставить аргументы синтаксису карты (&: 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]] 

Ответ 2

Для вашего примера можно сделать 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? ,

Ответ 3

Как сообщение, которое вы связали с подтверждением, 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.

Ответ 4

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

После ответа @rkon вы также можете сделать это:

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

Ответ 5

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

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

Ответ 6

Существует еще один родной вариант для перечислений, который, по-моему, довольно хорош только для двух аргументов. класс Enumerable имеет метод with_object, который затем возвращает другой Enumerable. Таким образом, вы можете вызвать & operator для метода с каждым элементом и объектом в качестве аргументов.

Пример:

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]

Ответ 7

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

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

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