← Back to Upcase

Ruby #inject


(Paul Haddad) #1

In this article, https://robots.thoughtbot.com/iteration-as-an-anti-pattern, I see this pattern with inject:

array.inject(:&)

What exactly is happening when using the ampersand as a symbol to inject? For example, this returns true:

[true, true, true].inject(:&)

while this returns false:

[true, false, true].inject(:&)


(Josh Mills) #2

:& is a shorthand for the Symbol#to_proc method.

#inject is used to calculate a single result from multiple inputs, which is why the result comes back as it does. You can get a clearer understanding from the Ruby docs on Enumerable#inject


(Joel Quenneville) #3

@paulh16 there are a few separate things going on here:

Note: I’m assuming you understand how inject works and are confused by the :& part.

TL DR;

inject(:&) calls the & method on all the values in your array. Under the hood, it’s evaluating: ((true & true) & true)

This should not be confused with a similar syntax that starts with &: (note the reversed order) that takes advantage of Symbol#to_proc and is commonly used by Rubyists instead of passing a block to a method.

Blocks

Many methods in Ruby allow you to pass them a block (inject being one of them). For example, if you wanted to sum an array of numbers, you could do:

[1,2,3,4].inject do |running_total, number|
  running_total + number
end

# => 10

Procs

Instead of passing a block, you can pass in a Proc (Ruby’s function object) as an argument. When passing a function as an argument instead of using a block, we need to prefix it with &.

sum = ->(x, y) { x + y }
# => #<Proc:0x007fc980c19160@(pry):4 (lambda)>

[1,2,3,4].inject(&sum)
#=> 10

Symbols

When passing an object instead of a block with &, Ruby will call to_proc on them before trying to evaluate the function. This means we can pass any object that responds to to_proc. Symbol is another type of object that responds to this method. Symbol#to_proc returns a proc object that will delegate the method named by the symbol to it’s arguments. For example:

sum = :+.to_proc
# => #<Proc:0x007f890c1433d0(&:+)>

sum.call(1,2) # this is how you invoke a Proc object
# => 3

[1,2,3,4].inject(&sum)
# => 10

Since to_proc gets called automatically, we can pass in symbols instead of procs:

[1,2,3,4].inject(&:+)
# => 10

The inject method

Any method in Ruby that accepts a block can use the Symbol#to_proc trick instead:

[1,2,3,4].map(&:even?)
# => [false, true, false, true]

[1,2,3,4].any?(&:negative?)
# => false

This style is commonly used with the various methods from the Enumerable module. It is used throughout the article you linked.

However, inject is special. From the docs (linked to by @trueheart78):

Combines all elements of enum by applying a binary operation, specified by a block or a symbol that names a method or operator.

Inject can take an optional symbol argument instead of a block. This means you can do:

[1,2,3,4].inject(:+)
=> 10

This is very similar to what we did before. However, notice that the argument is :+, not :&:+. The result is the same.

Your example

In addition to being used within method signatures to denote a block parameter, the & is a method defined on TrueClass and FalseClass to implement boolean logic:

true & true
#=> true
true & false
#=> false
false & true
#=> false
false & false
# => false

In your example [true, true, true].inject(:&), the symbol :& is passed to inject. When using Symbol#to_proc, the equivalent would be [true, true, true].inject(&:&). Both of these do the same thing: they AND all the values and return the result.

[true, true, true].inject(:&)
# => true

[true, true, true].inject(&:&)
# => true

Under the hood, it’s evaluating: ((true & true) & true)