Chaining & to_proc on symbol

无人久伴 提交于 2019-11-29 07:10:27

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)
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!