可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
It's well known to Rubyist &
will call to_proc
on a symbol, so
[:a, :b, :c].map(&:to_s)
is equivalent to
[:a, :b, :c].map { |e| e.to_s } # => ["a", "b", "c"]
Say I want to call another method right after to_s
, these two implementations will work:
[:a, :b, :c].map { |e| e.to_s.upcase }
[:a, :b, :c].map(&:to_s).map(&:upcase)
My question is, is there a way to chain the &
Symbol#to_proc
call in one parameter? Something like:
[:a, :b, :c].map(&:to_s:upcase)
Thanks!
回答1:
If you're only doing:
%i[a b c].map { |e| e.to_s.upcase }
then just use the block and get on with more important things. If you're really doing a chain of Enumerable calls and find the blocks too visually noisy:
%i[a b c].map { |e| e.to_s.upcase }.some_chain_of_enumerable_calls...
then you could toss your logic into a lambda to help clean up the appearance:
to_s_upcase = lambda { |e| e.to_s.upcase }
%i[a b c].map(&to_s_upcase).some_chain_of_enumerable_calls...
or throw it in a method and say:
%i[a b c].map(&method(:to_s_upcase)).some_chain_of_enumerable_calls...
Either way, you're giving your little bit of logic a name (which is pretty much all &:symbol
is doing for you) to make the code more readable and easier to understand. In the specific case of to_s.upcase
, this is all a bit pointless but these approaches are quite useful when the block gets bigger.
回答2:
You will need to define some method in advance, but this will have generality. You can do like this:
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)
...
I chose *
as a method for functional composition.
And if you think you might use a third symbol, you can define like:
class Proc
def * other
->x{call(x).send(other)}
end
end
回答3:
So just for fun (and to prove that almost anything is possible in ruby if one puts in a bit of effort) we could define a method on Symbol
(we'll call it Symbol#chain
) to provide this functionality and a little more
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
This can then be called like so
[: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!!!!"]]
Can even be used as a standalone
p = :to_s.chain([:split,'.'])
p.call(123.45)
#=> ["123","45"]
# Or even
[123.45,76.75].map(&p)
#=> => [["123", "45"], ["76", "75"]]
回答4:
While we're playing with syntax, how about monkey-patching Array with a to_proc
method?
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"]
See it on repl.it: https://repl.it/JS4B/1
回答5:
There is no way to chain using the symbol to proc.
However, you could monkey patch a method to the class you are mapping over that will do both, then call that.
class Symbol
def to_upcase_str
self.to_s.upcase
end
end
[:a, :b, :c].map(&:to_upcase_str)