CSC/ECE 517 Fall 2012/ch1b 1w58 am

From PG_Wiki
Jump to: navigation, search

Ruby Blocks, Iterators, Functional Idioms

This wiki-page serves as a knowledge source for understanding Ruby Blocks, Iterators, Functional Idioms.

Contents


Introduction

Ruby has simplified the way programmers use loops and iterators. Ruby helps programmers to use Don't Repeat Yourself (DRY) principle effectively by using blocks and defining iterators over collections.[1] This helps in minimizing the development work that programmers often find in any other O-O style programming language. In ruby, these iterators, blocks and functional idioms are explained as:

Iterators are Collection defined methods that helps to iterate over all the elements present in a Collection.[2] Ruby Collections are basically objects that store a group of data members of various types. Examples of Collection are arrays, hashes etc.
A block consists of chunks of codes with a name assigned to it.[3] For example,

  my_block { puts "Hello World" }

Functional idioms means constructs in ruby that mimics Functional Programming Language.

Iterators

Iterators in ruby can be written in following ways:

The times iterator

The times[1] iterator works similar to the for loop used in programming languages. As the name suggests it allows to loop over a chunk of code n number of times. For example:

   5.times { puts "Hello Viewers!" }

This code produces the following output:

   Hello Viewers!
   Hello Viewers!
   Hello Viewers!
   Hello Viewers!
   Hello Viewers!

Any chunk of code enclosed within the curly braces will execute 5 times in the above example.

The upto iterator

The upto[1] iterator is also similar to the for loop. However, the difference between times and upto iterator is that, upto allows the programmer to specify the start index m and the end index n of the loop. So, if we define an upto iterator as m.upto(n) { # do some work }, then the code in curly braces will be executed n - m + 1 number of times. For example:

   m = 45
   n = 48
   m.utpo(n) {puts "Hello Viewers! This is really cool, isn't it?"}

This code produces the following output:

   Hello Viewers! This is really cool, isn't it?
   Hello Viewers! This is really cool, isn't it?
   Hello Viewers! This is really cool, isn't it?
   Hello Viewers! This is really cool, isn't it?

Here the loop will be executed 48 - 45 + 1 = 4 number of times. If we had used times than 45.times would have printed the puts statement 45 number of times. Hence it allows us to make loop execution dynamic as compared to the times iterator which is relatively static.

The step iterator

The above iterators times and upto performs the loop execution in increments of 1. Step[1] iterator allows us to vary the increments of loop execution. It gives the programmers the flexibility to skip a few iterations of the loop if it is required as per the programmers logic. For example, consider the code:

   guess_my_string = "HfezlvlbomWyowrqlad"
   0.step(guess_my_string.length, 2) {|i| print guess_my_string[i]}

This code produces the following output:

   HelloWorld

In the above code, the variable i collects the iteration number and the print statement prints the character of the String guess_my_string at position i. Hence we get the above output.

The each iterator

All collections in Ruby have an each[1][2] method defined, that will allow programmers to go over all of the data members in collection for which the each method is called and do some operation with them. For example, consider the code:

   example = [82, 117, 98, 121, 32, 105, 115, 32, 103, 114, 101, 97, 116, 33]
   example.each {|i| print i.chr }

The above code prints the following output:

   Ruby is great!

In the above code the each iterator takes every element of the array example and prints its character equivalent.

The each iterator can also be used for hash traversal. Ruby defines the following methods for hash traversal:

  1. each_key
    This method returns a collection of all the keys present in the hash on which the each_key method was called.
  2. each_value
    This method returns a collection of all the values present in the hash on which the each_value method was called.
  3. each_pair
    This method returns a collection of all the key, value pairs present in the hash on which the each_pair method was called.

Consider the example which explains the behavior of all the above methods defined for hash.

   breakfast_menu = {"Martini" => 4, "apple pie" => 3, "pan cakes" => 5 }
   
   puts "Today's break fast menu -"
   breakfast_menu.each_key { |i| puts i }
   
   puts "\nTheir respective prices -"
   breakfast_menu.each_value { |i| puts "$#{i}" }
   
   puts "\nThe breakfast menu with their respective prices -"
   breakfast_menu.each_pair { |i, j| puts "#{i}: $#{j}" }

The above code produces the following output:

   Today's break fast menu -
   Martini
   apple pie
   pan cakes
   
   Their respective prices -
   $4
   $3
   $5
   
   The breakfast menu with their respective prices -
   Martini: $4
   apple pie: $3
   pan cakes: $5

In the above code, breakfast_menu defines a hash. It contains key, value pair. When each_key method is called on breakfast_menu, it prints all the keys present in the hash breakfast_menu. Similarly for each_value. For each_pair it prints all the key value pairs present in the hash. Hence the output.

The collect iterator

This simply returns all the elements of the collection. The collect[2] method works for arrays as well as hashes. For example:

   a = ["ruby", "rails", "ruby on rails"];
   b = Array.new
   b = a.collect{ |i| i.capitalize}
   puts b

This produces the following output:

   Ruby
   Rails
   Ruby on rails

In the above code, a.collect iterates over all the elements of a and returns a collection of all elements in a and assigns it to b

The map iterator

The map[4] method creates a temporary array and stores in it whatever value is returned from each iteration of the block of code on which the map method is called. Then that temporary array is returned as a collection. For example consider the ruby code

   fruits = [ "apple", "banana", "cherry" ]
   puts fruits.map { |i| i.reverse }

The above code produces the following output:

   elppa
   ananab
   yrrehc

In the above code the map method iterates over all the elements in fruits and reverse them. These reverse strings are stored in a temporary array which map method uses and returns that array when all the elements in fruits are iterated.


In ruby, there are many methods that iterate over a range of values. These iterators are so written that they take a code block as part of their calling syntax and then yield control to that code block. This code block is executed as many times as specified by the iterator. In all of the above examples, the code that is written in curly braces is the block that the iterators yield their control to.

Blocks

A Block[3] consists of a chunk of codes with a name assigned to it. Alternately, a block is a code which is passed to the iterator. These blocks can either be defined by enclosing them in curly braces or by using the do...end syntax[5] as shown below

   2.times { puts "This is simple a linear block" }
   2.times do
       puts "This is a block using do..end syntax"
       puts "You can add multiple statements inside the do...end syntax"
   end

Also, In the curly braces syntax multiple lines of code can be added but they need to be separated with a semicolon.

Block can also take parameters. For example consider the code shown below:

   5.times { |i| puts i}

In the above code i is passed as a parameter to the block. Thus any thing between the pipe symbols can be considered as parameters. Multiple parameters can also be passed to a block but they need to be separated with commas. For example,

  example = {"one" => 1, "two" => 2, "three" => 3}
  example.each_pair { |key, value| puts "#{key} : #{value}"}


In the all the above block examples, an interesting thing to note is that these blocks are not executed until a yield statement is encountered.[6] A block is always invoked from a function having the same name as that of the defined block. For, examples if there is a block with the name myblock, then there should be a function with the name myblock which invokes this block. For example, consider the code shown below:

   def block_testing
       puts "Inside function block_testing."
       yield
       puts "Back to block_testing function."
   end
   
   block_testing { puts "Yielding, to print within block." }

The code produces the following output:

   Inside function block_testing.
   Yielding, to print within block.
   Back to block_testing function.

In the above code, when the block_testing method executes, it prints the first statement, then yield is encountered and the control is passed to the block_testing block which prints the second statement. After the block finishes execution, the control is once again passed to the block_testing function which prints the third statement.

Defining your own iterator

Ruby allows programmers to define their own iterator other than the iterators listed above[7]. For example if a programmer wishes to define an iterator which prints an array in reverse order, then he can do so as shown below:

   class Array
       def reverse_iterate
           if block_given?
               current_index = self.size-1
               while current_index >= 0
                   yield self[current_index]
                   current_index -= 1
               end
           else
               print self.reverse
           end
       end
   end

In order to use the above iterator[7], we just need to call this iterator on an array as shown below.

   puts "Fall break begins in 10 secs ..."
   puts [0,1,2,3,4,5,6,7,8,9, 10].reverse_iterate {|i| puts i }
   puts ".. yay!! "

The above code produces the following output:

   Fall break begins in 10 secs ...
   10
   9
   8
   7
   6
   5
   4
   3
   2
   1
   0
   
   .. yay!!

In the above code, the reverse_iterator iterates the array in reverse order and prints the array elements as shown in the output.

Expression Orientation

Expression orientation[8] in ruby refers to the mechanism of applying a series of operation on collections ( e.g. arrays, hashes etc ) without actually modifying the original collection. For example, consider the sort method in ruby. The sort method when called on an array creates a temporary array having the same elements as that of the original array. It then sorts this temporary array and returns that. So, for the code shown below:

   a = [ 9, 8, 3, 6, 4]
   print a.sort
   print a

produces the following output:

   [3, 4, 6, 8, 9]
   [9, 8, 3, 6, 4]

From the output it is very clear that the original array is not modified while we can still perform a series of operations on the array a. Surprisingly, ruby also performs cascaded operations for ex,

   x = ["I", "love", "pancakes", "pancakes", "and", "only", "pancakes"]
   print x.uniq.reverse
   print x

this code gives the following output:

   ["only", "and", "pancakes", "love", "I"]
   ["I", "love", "pancakes", "pancakes", "and", "only", "pancakes"]

Here, the first operation x.sort removes duplicate occurrences of string "pancakes" from the temporary array which it creates to perform this operation and then the reverse operation reverses that temporary array to print the elements in the reverse order. However, the original array is still not modified and the next print.x statement prints the original array x.

One important thing to note here is that if directly reverse operation is used then the array gets modified. For example, for the code above, if later the programmer directly writes

   print x.reverse!
   print x

then we get the following output

   ["pancakes", "only", "and", "pancakes", "pancakes", "love", "I"]
   ["pancakes", "only", "and", "pancakes", "pancakes", "love", "I"]

Thus, the array x gets modified. Ruby is indeed crazy, isn't it?

Functional Idioms

Functional idioms refers to the various constructs in ruby that mimics Functional Programming Language. Most programmers argue that Ruby is actually an Object Oriented Programming Language and it does not behaves as per the functional programming paradigm. However, though the argument is open ended, Ruby is a multi-paradigm language that supports a functional style of programming, ( supporting functional style of programming doesn't make the language a Functional Programming language ). Ruby does supports the functional programming behavior of avoiding mutable states (Immutable and Mutable Object). To make the concept clear, consider the example shown below which uses the iterator "each". This type of iteration in ruby is actually a function because ruby passes a function ( block of code ) to be called on each item in the collection[9].

   puts "This is a Pythagorean triple!"
   [3, 4, 5].each { |num| print num }

In the above example[10], the function being passed is { |num| puts num }. In fact, the code shown below accomplishes the same behavior as done above:

   puts "This is a Pythagorean triple!"
   my_print_function = lambda { |num| print num }
   [3, 4, 5].each &my_print_function

Here, in the example shown above, we use the address of "my_print_function" to identify the lines of code that needs to be executed and for each element of the collection execute the function "my_print_function". So, in that respect, we can call the iterator each a functional idiom.

Now, this is very much from the functional programming paradigm, right? ( may be or may be not, lets keep this job for the debaters! ) Readers are encouraged to please refer here for further understanding of functional idioms.


For curious readers, both the above code produces the following output:

   This is a Pythagorean triple!
   [3, 4, 5]


Conclusion

Ruby provides such a flexible approach to use iterators as compared to C++ and Java that it is worth learning these iterators and to try out them while writing code ( or just for fun if programmers want to!). In ruby the iterator is simply a method that calls yield and passes control to a block of code associated with this iterator method. Unlike Java and C++, ruby does not requires the need to generate helper classes to carry the iterator state thus making it a transparent language. A beginner in ruby may think that the working of a block and yield is magical but actually, it is not! ( the wiki page just explains this magical behavior, right? ). As programmers keeps working on iterators, blocks and functional idioms they understand how easy it is to define your behavior without having the overhead of extra utilities. And this makes ruby programming so easy because you concentrate on getting the job done, not on building scaffolding to support the language itself.[11]

Topical References

  1. 1.0 1.1 1.2 1.3 1.4 Alan Skorkin. "A wealth of ruby loops and iterators" http://www.skorks.com/2009/09/a-wealth-of-ruby-loops-and-iterators
  2. 2.0 2.1 2.2 "Ruby Iterators" http://www.tutorialspoint.com/ruby/ruby_iterators.htm
  3. 3.0 3.1 "Ruby blocks" http://www.tutorialspoint.com/ruby/ruby_blocks.htm
  4. "Ruby for Newbies: Iterators and Blocks" http://net.tutsplus.com/tutorials/ruby/ruby-for-newbies-iterators-and-blocks/
  5. "More Afvanced ruby" http://www.skorks.com/2009/08/more-advanced-ruby-method-arguments-hashes-and-blocks/
  6. "Blocks basics" http://www.skorks.com/2009/08/more-advanced-ruby-method-arguments-hashes-and-blocks/
  7. 7.0 7.1 "Defining your own iterators" http://www.skorks.com/2009/09/using-ruby-blocks-and-rolling-your-own-iterators/
  8. "SaaS. 3.6 - Ruby Blocks, Iterators, Functional Idioms " http://www.youtube.com/watch?v=bRn91_Zonh4
  9. "Ruby a functional language" http://stackoverflow.com/questions/159797/is-ruby-a-functional-language
  10. "Functional programming in ruby" http://tech.rufy.com/2006/11/functional-programming-in-ruby.html
  11. "Ruby Blocks, Iterators and containers" http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_containers.html


Further Reading

Loops and Iterators : Ruby loops and iterators.

Ruby block basics : Basics about ruby blocks.

Iterators and Blocks : Read more about blocks and iterators.

Ruby cookbook : More about Ruby iterators.

Functional Idioms : Functional idioms explained.

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox