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!
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.
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
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"]]
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
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)
来源:https://stackoverflow.com/questions/34579603/chaining-to-proc-on-symbol