Fun with the Ruby & (unary ampersand) and String#to_proc


In trying to grasp the unary & operator in ruby, I ran across [Stupid Ruby Tricks: String#to_proc].

Therefore, I decided I had to further twist the language features to my own will.

Like many things in Ruby with duck-typing, the following syntax:

('a'..'z').map &:upcase

…makes use of the fact that Symbol has a #to_proc method, which means that the following call is an explicit version of the above call.

('a'..'z').map &:upcase.to_proc

If you wanted to allow for a shorthand notation for map, then you could add #to_proc to String, as follows:

class String
  def to_proc
    eval "Proc.new { |*args| args.first#{self} }"
  end
end

This allows for the following call:

[1,2,3].map &'*2'                     # [2, 4, 6]

…which is equivalent to:

[1,2,3].map { |*args| args.first*2 } # note: no space

…But what if I wanted to specify a library function to have the Array members passed to? (Certainly, this is venturing deeper into solving a problem that doesn’t exist.) Also, what if I want to pass an Array for the arguments?

class String
  def to_proc
    # assume an initial word character is a library function, otherwise evaluate as before
    if /^w/ =~ self
      eval "Proc.new { |*args| #{self}(*args.first) }"
    else
      eval "Proc.new { |*args| args.first#{self} }"
    end
  end

The above code will expand an embedded Array into an argument list:

[[2,2],[9,3],[64,4]].map &'Math.log'  # [1.0, 2.0, 3.0]
[1.0, 2.0, 4.0].map &'Math.sqrt'      # [1.0, 1.4142135623730951, 1.7320508075688772]
[1,2,3].map &'*2'                     # [2, 4, 6]
[[2,2],[9,3],[64,4], 4].map &'Math.log' # [1.0, 2.0, 3.0, 1.3862943611198906]

Leave a Reply

%d bloggers like this: