Можете ли вы предоставить аргументы синтаксису карты (&: метод) в 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, так что это сбивает с толку)