在符号上链接 & to_proc

Chaining & to_proc on symbol

Rubyist 众所周知 & 会在符号上调用 to_proc,所以

[:a, :b, :c].map(&:to_s)

等同于

[:a, :b, :c].map { |e| e.to_s } # => ["a", "b", "c"]

假设我想在 to_s 之后立即调用另一个方法,这两个实现将起作用:

[:a, :b, :c].map { |e| e.to_s.upcase }
[:a, :b, :c].map(&:to_s).map(&:upcase)

我的问题是,有没有办法在一个参数中链接 & Symbol#to_proc 调用?类似于:

[:a, :b, :c].map(&:to_s:upcase)

谢谢!

无法使用符号链接来触发。

但是,您可以将一个方法修补到您正在映射的 class 上,这将同时执行这两项操作,然后调用它。

class Symbol
  def to_upcase_str
    self.to_s.upcase
  end
end

[:a, :b, :c].map(&:to_upcase_str)

如果你只做:

%i[a b c].map { |e| e.to_s.upcase }

然后就使用该块并继续处理更重要的事情。如果您确实在执行一系列 Enumerable 调用并发现这些块在视觉上过于嘈杂:

%i[a b c].map { |e| e.to_s.upcase }.some_chain_of_enumerable_calls...

然后您可以将您的逻辑放入 lambda 以帮助清理外观:

to_s_upcase = lambda { |e| e.to_s.upcase }
%i[a b c].map(&to_s_upcase).some_chain_of_enumerable_calls...

或者把它扔到一个方法里说:

%i[a b c].map(&method(:to_s_upcase)).some_chain_of_enumerable_calls...

无论哪种方式,您都为您的一点点逻辑命名(这几乎是 &:symbol 为您所做的一切)以使代码更易读和更容易理解。在 to_s.upcase 的特定情况下,这有点毫无意义,但当块变大时,这些方法非常有用。

你需要提前定义一些方法,但是这样会有通用性。你可以这样做:

class Symbol
  def * other
    ->x{x.send(self).send(other)}
  end
end

[:a, :b, :c].map(&:to_s * :upcase)
[:a, :b, :c].map(&:to_s * :capitalize)
...

我选择*作为功能组合的方法。

如果你认为你可能会使用第三个符号,你可以这样定义:

class Proc
  def * other
    ->x{call(x).send(other)}
  end
end

所以只是为了好玩(并证明如果付出一点努力,在 ruby 中几乎任何事情都是可能的)我们可以在 Symbol 上定义一个方法(我们称它为Symbol#chain) 提供此功能以及更多

class Symbol
  def proc_chain(*args)
    args.inject(self.to_proc) do |memo,meth|
      meth, *passable_args = [meth].flatten
      passable_block = passable_args.pop if passable_args.last.is_a?(Proc)
      Proc.new do |obj|
        memo.call(obj).__send__(meth,*passable_args,&passable_block)
      end
    end
  end
  alias_method :chain, :proc_chain
end

然后可以这样调用

[:a, :b, :c].map(&:to_s.chain(:upcase))
#=> ["A","B","C"]
# Or with Arguments & blocks
[1,2,3,4,5].map(&:itself.chain([:to_s,2],:chars,[:map,->(e){ "#{e}!!!!"}]))
#=>  => [["1!!!!"], ["1!!!!", "0!!!!"], ["1!!!!", "1!!!!"],
#        ["1!!!!","0!!!!", "0!!!!"], ["1!!!!", "0!!!!", "1!!!!"]]

甚至可以单独使用

p = :to_s.chain([:split,'.'])
p.call(123.45)
#=> ["123","45"]
# Or even 
[123.45,76.75].map(&p)
#=> => [["123", "45"], ["76", "75"]]

虽然我们在玩语法,但用 to_proc 方法对数组进行猴子修补怎么样?

class Array
  def to_proc
    return :itself.to_proc if empty?
    ->(obj) { drop(1).to_proc.call(first.to_proc.call(obj)) }
  end
end

["foo", "bar", "baz"].map(&[:capitalize, :swapcase, :chars, ->a{ a.join("-") }])
# => ["f-O-O", "b-A-R", "b-A-Z"]

在 repl.it 上查看:https://repl.it/JS4B/1

我很惊讶没有人提到 Proc#<<Proc#>>:

[:a, :b, :c].map(&(:to_s.to_proc << :upcase.to_proc))
# => ["A", "B", "C"]
[:a, :b, :c].map(&(:upcase.to_proc >> :to_s.to_proc))
# => ["A", "B", "C"]

参考:https://ruby-doc.org/core-2.7.2/Proc.html#method-i-3C-3C