When to use Ampersand and/or Colon with Proc, Map, and Inject

So with Ruby permitting Procs called on Objects I’ve found sometimes a Colon Method will work, and sometimes you need a Ampersand Colon Method. For example, when I map &:upcase on a list of strings it works.

["a","b","c"].map(&:upcase) # = > ["A", "B", "C"] 1 2 3 [ "a" , "b" , "c" ] . map ( & : upcase ) # = > ["A", "B", "C"]

But if I try without the Ampersand I get:

["a","b","c"].map(:upcase) # ArgumentError: wrong number of arguments (1 for 0) 1 2 3 [ "a" , "b" , "c" ] . map ( : upcase ) # ArgumentError: wrong number of arguments (1 for 0)

So for mapping it seems we need Ampersand. And yes I’ve tested this with many methods and they are the same. But let’s look at inject.

["a","b","c"].inject(&:concat) # => "abc" ["a","b","c"].inject(:concat) # => "abc" 1 2 3 4 5 [ "a" , "b" , "c" ] . inject ( & : concat ) # => "abc" [ "a" , "b" , "c" ] . inject ( : concat ) # => "abc"

Here you can see both with and without Ampersand the concat method works. The reason for this is because of how inject works. Inject literally put the command between the values.

["a","b","c"].inject(:concat) == ("a").concat("b").concat("c") # => true 1 2 3 [ "a" , "b" , "c" ] . inject ( : concat ) == ( "a" ) . concat ( "b" ) . concat ( "c" ) # => true

So inject is always passing the next value in the array as a parameter and calls the method on the previous Array item.

When you see the Ampersand Proc &:method the way to think of it is the Object it’s being called on substitutes the Ampersand. So [“a”].map(&:upcase) will go over each item in the Array and place it in place of the Ampersand:

["a", "b" ,"c"].map(&:upcase) # becomes ["a".upcase, "b".upcase, "c".upcase] 1 2 3 4 [ "a" , "b" , "c" ] . map ( & : upcase ) # becomes [ "a" . upcase , "b" . upcase , "c" . upcase ]

So a good way to think about Ampersand is that’s where the Object will get placed. Map drops the method on each item with &: and inject will place the method between A and B with either &: or :

But wait! There is another use case of Proc being used with Ampersand.

putsy = proc {|i| puts i} ["a","b","c"].map(&putsy) # a # b # c # => [nil, nil, nil] ["a","b","c"].inject(&putsy) # a # # => nil 1 2 3 4 5 6 7 8 9 10 11 12 13 putsy = proc { | i | puts i } [ "a" , "b" , "c" ] . map ( &putsy ) # a # b # c # => [nil, nil, nil] [ "a" , "b" , "c" ] . inject ( &putsy ) # a # # => nil

Here we have a method that takes a block, so we can put Ampersand in front of the method without a Colon. But as you can see here it does something rather weird with inject. I tried to reproduce how inject was calling this by manually putting the &putsy Proc in, but I had no luck in getting the same result. So that remains a mystery to me. But we know with map the Proc gets handed to each element cleanly:

putsy.call "a" # a # => nil putsy["a"] # a # => nil 1 2 3 4 5 6 7 putsy . call "a" # a # => nil putsy [ "a" ] # a # => nil

We can try this same behaviour with a Lambda.

lamsy = lambda {|i| puts i} ["a","b","c"].map(&lamsy) # a # b # c # => [nil, nil, nil] ["a","b","c"].inject(&lamsy) # ArgumentError: wrong number of arguments (2 for 1) 1 2 3 4 5 6 7 8 9 10 11 lamsy = lambda { | i | puts i } [ "a" , "b" , "c" ] . map ( &lamsy ) # a # b # c # => [nil, nil, nil] [ "a" , "b" , "c" ] . inject ( &lamsy ) # ArgumentError: wrong number of arguments (2 for 1)

Here we can see inject blows up all-together. With this we know that we should make it a habit using inject with only a Colon. Think of inject as a method chain. You’re sticking the method in-between, so you won’t be using Procs in practice for inject… just methods for chaining.

["a","b","c"].inject(:+) == ("a").+("b").+("c") # => true 1 2 3 [ "a" , "b" , "c" ] . inject ( : + ) == ( "a" ) . + ( "b" ) . + ( "c" ) # => true

So when calling map with a Proc parameter (and not a block) know to use &: for calling the method on the Object. And just & for passing the Object as a block.

# &: calling method on Object ["a"].map(&:upcase) == ["a".upcase] # => true # & calling Proc with Object as a block ["a"].map(&putsy) == [putsy.call("a")] # => true 1 2 3 4 5 6 7 8 # &: calling method on Object [ "a" ] . map ( & : upcase ) == [ "a" . upcase ] # => true # & calling Proc with Object as a block [ "a" ] . map ( &putsy ) == [ putsy . call ( "a" ) ] # => true

And that should give you a great idea of when to use Ampersand and Colon’s with Procs, Maps, and Inject.

Visual Guide

# MAP ["a", "b", "c"].map(&:upcase) # &:upcase &:upcase &:upcase # | | | [ "a".upcase, "b".upcase, "c".upcase] ["a", "b", "c"].map(&my_proc) # &my_proc &my_proc &my_proc # \ \ \ [my_proc["a"], my_proc["b"], my_proc["c"]] # INJECT ["a", "b", "c"].inject(:concat) # :concat :concat # \|/ \|/ "a".concat "b".concat "c" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # MAP [ "a" , "b" , "c" ] . map ( & : upcase ) # &:upcase &:upcase &:upcase # | | | [ "a" . upcase , "b" . upcase , "c" . upcase ] [ "a" , "b" , "c" ] . map ( &my_proc ) # &my_proc &my_proc &my_proc # \ \ \ [ my_proc [ "a" ] , my_proc [ "b" ] , my_proc [ "c" ] ] # INJECT [ "a" , "b" , "c" ] . inject ( : concat ) # :concat :concat # \|/ \|/ "a" . concat "b" . concat "c"

UPDATE

(Inject, Procs, and Lambdas)

After some insightful input from comments both here and on Reddit I’ll clarify and update the use of Procs and Lambdas with Inject and Injects ability.

The problem with my previous Inject examples is that I had written my procs and lambdas to only handle one input variable. Inject deals with pairs at a time. Here’s an example:

paddy = proc {|a,b| a + b } ["a","b","c"].inject(&paddy) # => "abc" laddy = lambda {|a,b| a + b } ["a","b","c"].inject(&laddy) # => "abc" 1 2 3 4 5 6 7 8 paddy = proc { | a , b | a + b } [ "a" , "b" , "c" ] . inject ( &paddy ) # => "abc" laddy = lambda { | a , b | a + b } [ "a" , "b" , "c" ] . inject ( &laddy ) # => "abc"

As has clarified for me in the comments Lambda’s are strict in how many parameters you give them. However many you define them to take they will. Procs are a little looser in allowing additional parameters.

From the above example we see that Procs and Lambdas work perfectly well with inject when they are designed to handle 2 parameters. This is quite useful. So now for Inject I suggest Colon (:) for Methods and Ampersand (&) for procs/lambdas. For map it will either be both &: for method calls or & for procs/lambdas.

["a","b","c"].map(&:upcase) ["a","b","c"].map(&m_proc) # m_proc = proc {|i| i.upcase } ["a","b","c"].map(&m_lambda) # m_lambda = lambda {|i| i.upcase } ["a","b","c"].inject(:+) ["a","b","c"].inject(&i_proc) # i_proc = proc {|a,b| a + b } ["a","b","c"].inject(&i_lambda) # i_lambda = lambda {|a,b| a + b } 1 2 3 4 5 6 7 8 [ "a" , "b" , "c" ] . map ( & : upcase ) [ "a" , "b" , "c" ] . map ( &m_proc ) # m_proc = proc {|i| i.upcase } [ "a" , "b" , "c" ] . map ( &m_lambda ) # m_lambda = lambda {|i| i.upcase } [ "a" , "b" , "c" ] . inject ( : + ) [ "a" , "b" , "c" ] . inject ( &i_proc ) # i_proc = proc {|a,b| a + b } [ "a" , "b" , "c" ] . inject ( &i_lambda ) # i_lambda = lambda {|a,b| a + b }

You can also use procs designed to handle multiple values with nested Arrays:

[["a","b"],["c","d"],["e","f"]].map(&i_proc) # => ["ab", "cd", "ef"] 1 2 3 [ [ "a" , "b" ] , [ "c" , "d" ] , [ "e" , "f" ] ] . map ( &i_proc ) # => ["ab", "cd", "ef"]

For a last note on Inject I will quote Vincent Franco

Injects are easier to understand if you think of them as folds(a functional construct.) So think of inject working like this: ((((a,b),c),d),e) which is slightly different than the way you express them here.

I thank everyone for their feedback; both constructive or otherwise. It has been helpful for me and in turn for others as well. I hope this information was both insightful and useful for you! Please comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!

-Daniel P. Clark