2

I tried to shorten

values.map { |value| value.gsub!("\n", ' ') }

with

values.map(&:gsub!("\n", ' '))

but it gives me an:

SyntaxError:
...csv_creator.rb:40: syntax error, unexpected '(', expecting ')'
     values.map(&:gsub!("\n", ' '))

Anyone know what's going on?

Flip
  • 6,233
  • 7
  • 46
  • 75

3 Answers3

3

&:method notation is using #to_proc method, which is capable of converting symbol to Proc object. It can't be used as a shortcut if you need to provide additional arguments to called method.

Longer explanation about #to_proc can be found in separate answer: What does to_proc method mean?

Community
  • 1
  • 1
samuil
  • 5,001
  • 1
  • 37
  • 44
3

&:foo may erroneously be seen as &: plus foo (terms like "pretzel colon" reinforce this mistaken view). But no method foo is being called here. &:foo is actually & plus :foo, the latter being a plain symbol.

When calling a method, &object (without :) invokes object.to_proc (which is supposed to return a Proc) and passes the returned proc as a block argument to the method.

object often happens to be a symbol and Symbol#to_proc's implementation would look somehow like this in Ruby: (it's actually written in C)

class Symbol
  def to_proc
    proc { |object, *args| object.public_send(self, *args) }
  end
end

So this:

method(&:symbol)

effectively becomes this:

method { |object, *args| object.public_send(:symbol, *args) }

or, if method doesn't yield multiple values (like map), it's simply:

method { |object| object.public_send(:symbol) }

Obviously, you can't pass additional arguments via a symbol.

But ... object doesn't have to be a symbol. You could use another class with a custom to_proc implementation. Let's abuse Array for demonstration purposes:

class Array
  def to_proc
    method, *args = self
    proc { |obj| obj.public_send(method, *args) }
  end
end

This hack would allow you to write:

["foo\nbar", "baz\nqux"].map(&[:gsub, "\n", '-'])
#=> ["foo-bar", "baz-qux"]
Stefan
  • 109,145
  • 14
  • 143
  • 218
1

Alternatives

String#methods

Just to show what could be done : you could define String methods without arguments.

class String
  def replace_newlines!(replace = ' ')
    gsub!("\n", replace)
  end

  def replace_newlines(replace = ' ')
    gsub("\n", replace)
  end
end

p new_values = values.map(&:replace_newlines)
#=> ["1 2", "a b"]

p values.each(&:replace_newlines!)
#=> ["1 2", "a b"]

Sadly, refinements wouldn't work with to_proc.

Proc

Another possibility would be to define a new Proc, without monkey-patching String :

my_gsub = proc { |string| string.gsub("\n", ' ') }
p new_values = values.map(&my_gsub)
#=> ["1 2", "a b"]

each/map/gsub/gsub!

Note that map doesn't make much sense when used with ! methods. You should either :

  • use map with gsub
  • or use each with gsub!
Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • Good idea..but I don't think my boss would like me to mess around with the String class.. – Flip Jan 25 '17 at 14:07
  • Fair enough, let's say it's just to show what could be done. The advice on `map` and `gsub!` still stands, though. – Eric Duminil Jan 25 '17 at 14:12
  • +1 for that..unfortunately your answer doesn't answer my main question (why it wouldn't work), so I can't accept it as the best answer, even though it has more additional info. – Flip Jan 25 '17 at 14:21
  • Sure, no problem. @samuil had a valid answer before I posted mine. I just wanted to add a few points that wouldn't fit in a comment. – Eric Duminil Jan 25 '17 at 14:23
  • Very good points, I have to say. Neither did I know or think about `map` not making sense with !-methods nor did I know about refinements. Lessons learned! – Flip Jan 25 '17 at 14:28
  • Sorry to come back a bit delayed, but WHY is map not making sense with gsub!? Ahwell, I will open a new question.. – Flip Jan 25 '17 at 14:57
  • http://stackoverflow.com/questions/41854955/ruby-is-it-true-that-map-generally-doesnt-make-sense-with-bang-methods – Flip Jan 25 '17 at 15:11