January 1, 2015 by Daniel P. Clark

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

But if I try without the Ampersand I get:

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

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

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]

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 

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

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)

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

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

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"

 

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"

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 }

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

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

#howto#inject#lambda#map#proc#ruby#syntax

Comments

  1. Jeff Dickey
    January 1, 2015 - 10:50 pm

    Nice summary. BTW, I suspect you’ll get more keyword hits for `lambda` than `lamda`. 🙂

  2. Vincent Franco
    January 2, 2015 - 1:51 pm

    There are some issues with the logic in this article.

    First:
    [‘a’,’b’,’c’].inject(&putsy)
    “But as you can see here it does something rather weird with inject.”

    Reason:
    Inject is an accumulator that assigns expression of your block to memo. The reason you are getting “a” and then nothing is because the result of (puts “a”) is nil. So you are assigning nil to next memo, from there you are nil for every iteration of the accumulator. Because you are using the api of inject incorrectly (A proc with 1 arg).

    Second:
    [‘a’,’b’,’c’].inject(&lamsy)

    Reason:
    Again accumulator… Slight difference this time. You are using a lambda. Which is strict on arguments. Inject is calling with 2 args. But since you are using a lambda…

    Here’s an example:
    ->(a) { puts a }.call(1, 2) #=> ArgumentError: wrong number of arguments (2 for 1)
    proc { |a| puts a }.call(1, 2) #=> 1

    Now you know the difference between a proc and a lambda and here’s the doc for inject: http://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-inject

    That knowledge takes care of the things you were confused about.

    • Daniel P. Clark
      January 2, 2015 - 4:35 pm

      Thank you for your input! Your insights do clear quite a bit up for me. When I was trying to duplicate injects behavior I was experimenting with inline consecutive Procs/Lambdas but not getting the same results. As you have shown I was not performing the same way that inject was performing it.

      Also according to some of the Reddit Feedback I was a bit brash in saying “With this we know that we should make it a habit using inject with only a Colon” after the lambda blew up. I wasn’t concluding that because of the lambda, I meant it more as an accumulative results of previous examples. To use Colon+Method, aka a Symbol, keeps more clarity in what is being done. But this can’t be the case in using a proc or lambda with inject which I now know “can” be handed to inject if they’re designed for two values. So my recommendation should be to use symbol syntax in general for methods on inject, but obviously not for procs/lambdas.

      With your input I now see that I could write procs and lambdas to return values to continue the process. I’ll look into appending an update to the end of this article with clarifications, and I’ll try to address the Reddit responses as well.

  3. Matt
    January 8, 2015 - 3:51 pm

    Under your Visual Guide, I think you have a typo in that you’re using brackets instead of parentheses. You have:

    [my_proc[“a”], my_proc[“b”], my_proc[“c”]]

    whereas I think you mean:

    [my_proc(“a”), my_proc(“b”), my_proc(“c”)]

    • Daniel P. Clark
      January 8, 2015 - 8:02 pm

      Ruby uses [] to be the same as .call() on Proc objects. So my_proc[“a”] is equivalent to my_proc.call(“a”).

      • Matt
        January 9, 2015 - 1:31 pm

        Very interesting, that’s news to me! Thanks for pointing that out. 🙂

Leave a Reply

Your email address will not be published / Required fields are marked *