These articles are written by Codalogic empowerees as a way of sharing knowledge with the programming community. They do not necessarily reflect the opinions of Codalogic.
I mainly program in C++. I also use C# for Windows GUI programming, PHP for web work and a scripting language for odd tasks such as analysing text files or running tests.
I have been using Perl in the scripting language role. However, even I had to admit that that was getting long in the tooth and it would be good for my skill set to move onto something more modern. The logical choice was Python. I’ll admit that I never really got on with Python. The sorts of things I wanted to do often seemed harder to do in Python than in Perl. But Python seemed to be the way things should be done, so I decided to persevere. Then, by chance, I got involved in a project with someone who was using Ruby [RubyHome]. After an initial “whatever”, it has been love at first sight ever since!
I tell you this to put the rest of the article into context. I’m not an experienced Ruby programmer, and like an infatuated teenager, I may well be blind to any warts the language might have.
There are many Ruby tutorials online, so I don’t intend to teach you Ruby here. Instead, my aim is to help you recognise a Ruby program when you see one, try to pique your interest in learning more about Ruby, and cover a few things that might be intriguing to a C++ (and possibly Python) programmer not familiar with Ruby.
You may already have Ruby on your system if you are using Linux. If not, you should find a suitable download option at [RubyDownload]. In addition to providing the ability to run Ruby programs on your system, you should also find an interactive Ruby shell called irb
. This is very handy for experimenting with Ruby, and you may wish to use it to play around with some of the examples below (most of which you should find at [Examples]).
The obligatory “Hello, World!” program looks as follows in Ruby:
puts "Hello, World!"
It’s always a good sign when a scripting language allows “Hello, World!” to be a one-liner, but it doesn’t tell us much about the language. So, in the spirit of feature creep, let’s look at the following:
# A class to say Hello
class Greeter
def initialize( who )
@who = who
end
def greet
if @who =~ /^\s*PETE\s*$/i
puts "Hello author"
else
named_greeting
end
end
private def named_greeting
puts "Hello #{@who}"
end
end
greeter = Greeter.new "reader"
greeter.greet
Greeter.new( " Pete " ).greet
Working through the example, comments start with the #
character and continue to the end of the line.
Class names (i.e. Greeter
) must begin with a capital letter, and, by convention, use PascalCase.
Chunks of code (a ‘block’ has a special meaning in Ruby so I’m avoiding its usage here) typically start with a keyword such as class
, def
, if
, while
and continue until a matching end
keyword.
Indenting is not particularly important in Ruby. The Ruby style guide specifies two spaces per tab. I’m yet to get comfortable with that, but I’ve adopted it here as it’s more idiomatic, and better for presentation in a print publication.
The def
keyword defines a method. Method names typically are all lower-case and sub-words are underscore separated. The last character can also be one of ?
, !
or =
. The initialize
method is analogous to a constructor in C++, and is called when a new instance of an object is created.
The scope of variables in Ruby is indicated by an optional prefixed character. Without a prefix, a variable is local to the chunk it is defined in. Variables prefixed by a single @
character have object instance scope. Variables prefixed by @@
have class scope, similar to C++ static
class variables. Variables prefixed by $
are global. So, the line @who = who
translated to Python would look like self.who = who
. Variables names must begin with a lower-case letter or underscore, and, by convention, are all lower-case with sub-words separated by underscores (e.g. my_variable
). (Constants on the other hand must start with an upper-case letter, and are usually all upper-case along with underscore separators, e.g. MY_CONSTANT
.)
Moving onto the greet
method, we get our first glimpse of how minimalist and token free Ruby can be. There are no brackets around the if
clause and there is no delimiter such as then
or :
to explicitly mark the end of the condition. One of the goals of the Ruby designer (Yukihiro Matsumoto, or Matz to his friends) was to make programming faster and easier. One aspect of this is to make the interpreter work harder in order to make life for the programmer easier. Typically a Ruby expression ends at the end of a line, unless there is some kind of binary operator, or a ,
to suggest that there is more on the next line. Sometimes additional brackets are needed for disambiguation (and semi-colons can be used to terminate expressions), but the preferred Ruby style is to avoid them if possible. This can take a bit of getting used to, but after a while the result looks more narrative like than typical C++ programs.
The condition of the if
expression shows that regular expressions have first-class support in Ruby. Those with a Perl background will find this syntax familiar.
The puts
method call should be familiar to C++ programmers, although you may be excused for having forgotten about it. puts
prints its arguments to standard output, followed by a newline sequence. If you don’t want the newline characters, use print
instead.
Moving to the else
clause we have a call to the named_greeting
method. Unlike in Python, there is no need to prefix a call to another method in the same object with self.
.
The named_greeting
method definition itself is preceded by the private
method, showing Ruby gives you the ability to control access to object methods. Note that private
isn’t a keyword like in C++, but a method call. There’s some Ruby subtlety here which is too detailed to go into in this article. Suffice to say that the use of private
on the same line as a def
will make only that method private (similar to Java and C#’s usage), whereas private
on its own line (a call to private
with no arguments) will make all following methods private (similar to C++’s usage).
The puts
line in named_greeting
shows Ruby’s syntax for string interpolation. Many scripting languages support string interpolation, but Ruby takes this to the max. The code between the opening #{
and closing }
can be any expression and is not limited to just variables. It can include operators, method calls, and even if
statements. If the result of the expression is not a string, then the interpreter will automatically convert it to one.
The class definition described creates a runtime object called Greeter
. That object includes a method called new
. When the new
method is called on the Greeter
object it creates a new object that conforms to the class definition. It then calls the newly created object’s initialize
method with the arguments given in the new
method call. Note again that there are no brackets around the arguments to the new
method call.
In the example, we assign a reference to the new object to the greeter
variable.
Once we have a reference to an object instance, we can invoke methods on it as shown by the expression greeter.greet
.
As you might expect, Ruby is garbage collected so there’s no need to clean up any objects that have been created.
Hopefully it won’t be a surprise to you that the output of the program is:
Hello reader
Hello author
Now you hopefully have a feel for what a Ruby program looks like, let’s have a closer look at a few things that might be intriguing to a programmer who only knows C++, and things that are more unique to Ruby.
Everything in Ruby is an object. There are no primitive, machine word level types such as char
, int
and float
like you find in C++. Consequently, you can call methods directly on explicit values such as floating-point numbers. For example, 1.5.round
evaluates to 2
.
All classes are open to extension in Ruby and are similar to C# partial classes. For example, you can augment the Float class with your own methods (although this isn’t a recommendation to do it). The following program outputs 4.5
:
class Float
def triple
3 * self
end
end
puts 1.5.triple
It may look like the usage of puts
above is a free-standing function. But it is actually a member of the Object
class from which all objects derive. The other ‘fudge’ is that the outer-most level of execution takes place within the scope of an automatically defined, but largely hidden, object called main
.
In addition to numbers, strings and the regular expressions already mentioned, Ruby also supports arrays and hashes using the handy syntax you’d expect from a scripting language. Additionally, Ruby has a range type which captures a start value and an end value within a single object. I’ll touch on ranges in a bit, but here’s some examples of the syntax for these types:
my_array = [ 1, 2, 3, "Four", 5 ]
my_hash = { "author" => "Pete", "reader" => "you" }
my_range = 0..10
Ruby will automatically convert between numbers of different types (for example, integer to floating point number) as the operations require. But unlike some languages, outside of string interpolation, it won’t automatically convert to and from numerical types and strings.
As with Python, but not with Perl, Ruby has a Bignum
type that can store integers of any size. If integer operations get too big to fit within a native machine sized integer, they will be transparently converted to a Bignum
. This is shown in the following code, where**
is the ‘to the power of’ operator, and the output is shown in the comments:
puts 2**4 # 16
puts (2**4).class # Fixnum
puts 2**70 # 1180591620717411303424
puts (2**70).class # Bignum
puts (2**70/
2**60).class # Fixnum
Then there is true
, false
and nil
. nil
is equivalent to C#’s null
and Python’s None
. Only false
and nil
are treated as false in conditional expressions. Everything else is treated as true, including 0
, empty strings and empty arrays. Those familiar with Python and Perl might find the latter surprising, but it does seem to fit in with the language quite well. If nothing else, it’s easy to remember!
One of the more unique features of Ruby is the concept of a ‘block’. Similar to lambdas, any method can be given a block which it can call with a set of parameters, and receive a result back. The block
follows a method call, and is either contained between opening and closing braces, or the do
and end
keywords. The former syntax is typically used for one-line (or even in-line) blocks, and the latter for multi-line blocks. The arguments to the block are delimited by a pair of |
pipe characters, and the value of the last expression executed in the block is returned to the calling method. For example, using the variables set up previously:
my_array.each { |x| print "_#{x}" }
puts # Put new line at end of above
my_hash.each do |role, name|
if role == "reader"
puts "#{name}, Thanks for reading"
end
end
Outputs:
_1_2_3_Four_5
you, Thanks for reading
Blocks are not limited to iterating through arrays and hashes.
If the File.open
method is given a block, it will close the opened file on return from the block. As such this usage scenario is similar to C#’s using
and Python’s with
constructs.
In combination with suitable methods, blocks are often used as an alternative to conventional counted for
loops. In place of C++ like:
for (int i=0; i<10; ++i)
std::cout << "_" << i;
you can do any of these (wherein the last one is an example of the range type I mentioned earlier):
10.times { |x| print "_#{x}" }
0.upto(9) { |x| print "_#{x}" }
(0..9).each { |x| print "_#{x}" }
Blocks, and the fact that most methods return something ‘useful’, opens up the way to a more functional coding style. Among many functional-like methods, Ruby supports map
, select
and reduce
on arrays. Using these, if you wanted to know the sum of the odd squared numbers, for the numbers 0 to 100, you could do:
puts (0..100).map { |x| x * x }
.select { |x| x.odd? }
.reduce { |acc,x| acc + x }
Why Ruby uses select
rather than the more traditional filter
I’m not sure. If it bothers you though, you can do class Array; alias filter select; end
and use filter
in your code instead.
One thing that perhaps should be said here is that I haven’t been able to see how to make an expression like the above ‘lazy’ in the same way that Haskell does. Each method call creates a new array before the next method acts on it, rather than just acting on iterators. This won’t cause problems in many cases, but does prevent Haskell-like solutions of the form ‘for all positive integers…’.
There’s much more to Ruby than I’ve been able to cover here. Ruby has parallel assignment, slices, exceptions, threads and coroutines.
It also has an extensive library of third-party code in the form of RubyGems [RubyGems], and a dependency management system called Bundler [Bundler] that ensures that the correct versions of the Gems are installed for your program.
If you want to explore further, there are numerous Ruby tutorials on-line (e.g. [RubyTutorials]). I have found “The Ruby Programming Language” book [TRPL] to be very good. This is a depth-first look at the language that assumes you already know a bit about programming. It feels as close to a language definition as you can get without having to be a language lawyer. The Ruby documentation [RubyDocs] is good, but I find it hard to navigate. Hence, I tend to use Google as it’s front-end! Googling will also bring you to Stack Overflow. Perhaps because Ruby is slightly less common than other languages, the answers for Ruby seem to be of a higher quality than you might find for other languages. The irb
interactive Ruby shell is also a fun way to poke around to test your understanding.
I hope I’ve given you enough to be able to recognise a Ruby program. Possibly you’re now interested in finding out a little more information. I can understand that some people might find the various naming constraints difficult to get over, but in my case I had already adopted similar rules, and so wasn’t bothered. My biggest gripe is that if your code changes a constant, the Ruby interpreter will only generate a warning rather than an error. I console myself that, for the sorts of things I use Ruby for, I don’t need a lot of constants. Those wanting to make more extensive use of Ruby might consider this more of an issue.
Overall, I’ve found code solving similar problems is simpler and clearer in Ruby than in other languages. As such, it has exceeded my expectations and I hope it will for you too.
[Bundler] http://bundler.io/
[Examples] https://github.com/codalogic/accu-ruby
[RubyDocs] http://ruby-doc.org/core-2.4.1/
[RubyDownload] https://www.ruby-lang.org/en/downloads/
[RubyGems] https://www.ruby-lang.org/en/libraries/
[RubyHome] https://www.ruby-lang.org/en/
[RubyTutorials] https://www.ruby-lang.org/en/documentation/
[TRPL] Flanagan, D & Yukihiro Matsumoto, Y (2008), The Ruby Programming Language, O'Reilly.
February 2023
January 2023
December 2022
November 2022
October 2022
September 2022
August 2022
November 2021
June 2021
May 2021
April 2021
March 2021
October 2020
September 2020
September 2019
March 2019
June 2018
June 2017
August 2016