Chaining & to_proc on symbol

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"]]

Tags:

Ruby

Proc

Symbols