Getting Started With Ruby - A Functional Language |
Written by Mike James | ||||
Wednesday, 03 April 2013 | ||||
Page 1 of 3 Ruby has many faces - well just two main ones really, object oriented and functional. Everything in Ruby is an object and every expression returns a value. Functional thinking isn't as easy to acquire when you are trying to learn a new language so let's take a careful look at functional Ruby. This is the second article in a short series on getting started with Ruby. If you haven't got Ruby installed or if you want to know the basics of the language then read: Getting Started With Ruby: Object-Oriented Approach From here on in it is assumed that you know the basics of object-oriented Ruby. Ruby as a functional languageAlthough Ruby is often presented at first as an object-oriented language it has a lot of features that make it a good and practical functional language. Of course there are many who would say that Ruby really isn't a functional language at all, in the sense that it does miss some of the hardline features present in functional languages such as Haskell. However, there are features of Ruby that can only be described as functional. For example, it uses lazy evaluation and iterators to replace the use of explicit for loops and this makes it feel very different to a programmer used to the imperative style. Without some idea of how these features fit together, it can seem like a collection of odds and ends. Let's start with the most puzzling - the block. The BlockAny method can be passed a block of code that it can optionally execute at any point it cares to. This might seem like a strange idea, but it is very useful and is a direct consequence of Ruby being a dynamic language. The code block is perhaps one of the features that sets Ruby apart from other dynamic languages. Other languages make use of lambda expressions or simply make the function a first class object. Ruby uses an approach that is half way between these two alternatives. The block is a chunk of code that is wrapped by an object. This makes it possible to treat code as a first class object or to write standard looking lambda expressions - or if you want to you can simply ignore the issue and just think in terms of code blocks. For a dynamic language, the distinction between code and data is much less clear than in a static language. Because code is interpreted it can be modified as the program runs and one moment treated as data and next code. To see a block in action try the following simple program:
The block is added to the end of the method call after all of the standard parameters if there are any. It takes the form of any executable instruction withing a pair of curly braces:
If you run the program you will see the Start and End message that the method displays but no sign of the block. To make use of a block the method has to call it using the yield command. This is just like a method call and when the block is complete control passes back to the statement following the yield. If you change the method definition to:
and re-run the program you will see Start myMethod, MyBlock and End myMethod. So a block can only be written as part of a method call and it is treated as if it was an extra parameter to the method passing in the code. Internal iteratorsYou can run the block of code as many times as you like by repeating the yield statement and this indeed is the most common usage pattern as blocks and yield are mostly used to implement iterators. For example the following method is a trivial "repeat twice" iterator:
If you run the program you will see that the block is called twice. In a more extensive example you would want to pass values to the block and this needs a block parameter:
You specify parameters using vertical bars but apart from this everything works like a standard parameterized method. To pass values to parameters you simply include them following the yield, again just like a parameter call:
Now the program displays 1 followed by 2. Iterators are used by most Ruby objects to allow the user to scan through any collections they might hold. For example, the array object has an "each" iterator which will run a block on each of its elements:
The fact that everything is an object makes iterators almost the standard way of implementing loops in Ruby even though it does have a full complement of control structures such as for, while, until etc. External iteratorsHowever, notice that iterators, or more exactly internal iterators of the sort described above, have a serious shortcoming - you can only easily iterate through one object's collection at a time. Now consider how you would compare two collections for equality or add two arrays element by element? Not easy. To do this sort of task you need an external iterator which allows the client code, e.g. the role played by the block, to control the iteration. External iteration is what you are most familiar with because it is typified by the standard for loop. Basically an external iterator supplies you with a "next" method which you can call to get the next element. This makes it possible to step through multiple collections by starting separate iterations on each object and moving through each iteration using next. If you call an iterator without a block then it returns an enumeration object which can be used to step through the collection using its next method. So for example to step through the elements of an array you would use:
As well as iterators blocks are used to provide many of the familiar functional approaches to coding. For example:
The map method takes the block and applies it to each of the elements of the collection. In this case the result is 1,4,9,16 as each integer is squared. Notice that this is subtly different from:
which returns 1,2,3,4 rather than the result of the block i.e. you can specify a block but the it is the iterator which returns the result. |
||||
Last Updated ( Wednesday, 03 April 2013 ) |