Major Productions
Major Productions

Reputation: 6042

Some simple Ruby questions - iterators, blocks, and symbols

My background is in PHP and C#, but I'd really like to learn RoR. To that end, I've started reading the official documentation. I have some questions about some code examples.

The first is with iterators:

class Array
  def inject(n)
    each { |value| n = yield(n, value) }
    n
  end

  def sum
    inject(0) { |n, value| n + value }
  end

  def product
    inject(1) { |n, value| n * value }
  end
end

I understand that yield means "execute the associated block here." What's throwing me is the |value| n = part of the each. The other blocks make more sense to me as they seem to mimic C# style lambdas:

public int sum(int n, int value)
{
    return Inject((n, value) => n + value);
}

But the first example is confusing to me.

The other is with symbols. When would I want to use them? And why can't I do something like:

class Example
  attr_reader @member

  # more code
end

Upvotes: 6

Views: 671

Answers (5)

Venkatesh Dhanasekaran
Venkatesh Dhanasekaran

Reputation: 344

First you need to understand where to use symbols and where its not.. Symbol is especially used to represent something. Ex: :name, :age like that. Here we are not going to perform any operations using this. String are used only for data processing. Ex: 'a = name'. Here I gonna use this variable 'a' further for other string operations in ruby. Moreover, symbol is more memory efficient than strings and it is immutable. That's why ruby developer's prefers symbols than string.

You can even use inject method to calculate sum as (1..5).to_a.inject(:+)

Upvotes: 0

Matheus Moreira
Matheus Moreira

Reputation: 17020

In the inject or reduce method, n represents an accumulated value; this means the result of every iteration is accumulated in the n variable. This could be, as is in your example, the sum or product of the elements in the array.

yield returns the result of the block, which is stored in n and used in the next iterations. This is what makes the result "cumulative."

a = [ 1, 2, 3 ]
a.sum  # inject(0) { |n, v| n + v }
# n == 0; n = 0 + 1
# n == 1; n = 1 + 2
# n == 3; n = 3 + 3
 => 6

Also, to compute the sum you could also have written a.reduce :+. This works for any binary operation. If your method is named symbol, writing a.reduce :symbol is the same as writing a.reduce { |n, v| n.symbol v }.

attr and company are actually methods. Under the hood, they dynamically define the methods for you. It uses the symbol you passed to work out the names of the instance variable and the methods. :member results in the @member instance variable and the member and member = methods.

The reason you can't write attr_reader @member is because @member isn't an object in itself, nor can it be converted to a symbol; it actually tells ruby to fetch the value of the instance variable @member of the self object, which, at class scope, is the class itself.

To illustrate:

class Example
  @member = :member
  attr_accessor @member
end

e = Example.new
e.member = :value
e.member
 => :value

Remember that accessing unset instance variables yields nil, and since the attr method family accepts only symbols, you get: TypeError: nil is not a symbol.

Regarding Symbol usage, you can sort of use them like strings. They make excellent hash keys because equal symbols always refer to the same object, unlike strings.

:a.object_id == :a.object_id
 => true
'a'.object_id == 'a'.object_id
 => false

They're also commonly used to refer to method names, and can actually be converted to Procs, which can be passed to methods. This is what allows us to write things like array.map &:to_s.

Check out this article for more interpretations of the symbol.

Upvotes: 3

user587717
user587717

Reputation:

Your confusion with the first example may be due to your reading |value| n as a single expression, but it isn't.

This reformatted version might be clearer to you:

 def inject(n)
   each do |value|
     n = yield(n, value)
   end
   return n
 end

value is an element in the array, and it is yielded with n to whatever block is passed to inject, the result of which is set to n. If that's not clear, read up on the each method, which takes a block and yields each item in the array to it. Then it should be clearer how the accumulation works.

attr_reader is less weird when you consider that it is a method for generating accessor methods. It's not an accessor in itself. It doesn't need to deal with the @member variable's value, just its name. :member is just the interned version of the string 'member', which is the name of the variable.

You can think of symbols as lighter weight strings, with the additional bonus that every equal label is the same object - :foo.object_id == :foo.object_id, whereas 'foo'.object_id != 'foo'.object_id, because each 'foo' is a new object. You can try that for yourself in irb. Think of them as labels, or primitive strings. They're surprisingly useful and come up a lot, e.g. for metaprogramming or as keys in hashes. As pointed out elsewhere, calling object.send :foo is the same as calling object.foo

It's probably worth reading some early chapters from the 'pickaxe' book to learn some more ruby, it will help you understand and appreciate the extra stuff rails adds.

Upvotes: 1

Frederick Cheung
Frederick Cheung

Reputation: 84114

def inject(accumulator)
  each { |value| accumulator = yield(accumulator, value) }
  accumulator
end

This is just yielding the current value of accumulator and the array item to inject's block and then storing the result back into accumulator again.

class Example
  attr_reader @member
end

attr_reader is just a method whose argument is the name of the accessor you want to setup. So, in a contrived way you could do

class Example
  @ivar_name = 'foo'
  attr_reader @ivar_name
end

to create an getter method called foo

Upvotes: 1

jsinger
jsinger

Reputation: 807

For the definition of inject, you're basically setting up chained blocks. Specifically, the variable n in {|value| n = yield(n, value)} is essentially an accumulator for the block passed to inject. So, for example, for the definition of product, inject(1) {|value| n * value}, let's assume you have an array my_array = [1, 2, 3, 4]. When you call my_array.product, you start by calling inject with n = 1. each yields to the block defined in inject, which in turns yields to the block passed to inject itself with n (1) and the first value in the array (1 as well, in this case). This block, {|n, value| n * value} returns 1 == 1 * 1, which is set it inject's n variable. Next, 2 is yielded from each, and the block defined in inject block yields as yield(1, 2), which returns 2 and assigns it to n. Next 3 is yielded from each, the block yields the values (2, 3) and returns 6, which is stored in n for the next value, and so forth. Essentially, tracking the overall value agnostic of the calculation being performed in the specialised routines (sum and product) allows for generalization. Without that, you'd have to declare e.g.

def sum
  n = 0
  each {|val| n += val}
end

def product
  n = 1
  each {|val| n *= val}
end

which is annoyingly repetitive.

For your second question, attr_reader and its family are themselves methods that are defining the appropriate accessor routines using define_method internally, in a process called metaprogramming; they are not language statements, but just plain old methods. These functions expect to passed a symbol (or, perhaps, a string) that gives the name of the accessors you're creating. You could, in theory, use instance variables such as @member here, though it would be the value to which @member points that would be passed in and used in define_method. For an example of how these are implemented, this page shows some examples of attr_* methods.

Upvotes: 1

Related Questions