14 Languages - Ruby
2015-02-02
This is all the Pragmatic Programmers fault. If they hadn't had a 50% discount on all ebooks, I wouldn't have started on this mad journey. Now, I've bought Seven languages in seven weeks and Seven more languages in seven weeks, and decided read the books, do all exercises in them plus some katas for each language and write a blog post about each language. All in the first six months of 2015. How am I going to find time to do that? I'm also supposed to work on the math app and the site. Well, well. We'll see if it works out or not, but at least I'm going to give it a try.
First a little note about the book Seven languages in seven weeks. Each language is presented in a separate chapter. Each chapter (at least so far...) is divided into an introduction to the language, three 'days' in which the author presents the important stuff, and a summary. In the end of each day-section there are some questions to be answered (mostly by googling, the answers are most often not to be found in the book) and some exercises to be done. The questions and exercises, which so far haven't been that difficult to complete, are meant to deepen the reader's understanding of the language.
It seems to me, after having read 1.5 chapters, that the author in the two first 'Day'-sections presents the most common building blocks of the languages, how to construct if-clauses, loops and so forth. The third day is devoted to something that he feels is a particular strength of the language. For Ruby for example, the topic of the third day section is metaprogramming.
I don't expect to become a LanguageX-expert by reading some 50 or so pages in a book about that language. My expectations after having read the chapter about a language are that I know the following:
- That the language exist (that's an easy one ;).
- What kind of language (functional, object oriented, strongly typed, weakly typed, compiled, interpreted, ...) it is.
- How to write simple programs using the most common language constructs.
- What the main strengths and weaknesses of the language are.
That was that about the book. Now over to the first language - Ruby!
The first language - Ruby
Ruby is an object-oriented, dynamically and strongly typed, interpreted script language.
OK. So what does that mean? To me it means the following:
- Object-oriented: the data and behavour of the programs written in the language is encapsulated in objects. In Ruby everyting, even the primitive numbers is an object.
- Strongly typed: the types of the objects are checked and an error message is given if a type error is found.
- Dynamically typed: the type checking is executed at runtime, not at compile time (as is done statically typed languages)
- Interpreted: an interpreter is used to execute the programs. As opposed to a compiled language, in which a compiler is applied to the source code to transform it into a format that can be directly executed.
- Script language: a language in which it is possible to write scripts. I define a script as a program written to automate a task. Moreover, to me a script is a program that does not have to contain a main() method (or something similar). In its simplets form, it contains just a list of statements in a file and those statements are executed in order.
In addition to being strongly- and dynamically typed, Ruby is also duck typed. This means that (as you probably already know) it the actual fields and methods of object that determines what operations that are possible to invoke on the object, NOT its formal type. E.g. if we have two classes File
and Door
both of which contain an open()
operation. we can assign an instance of eiher of them to a variable and then use that variable it invoke the open()
operation regardless of it points to a File
or a Door
. As both classes contain an open
method, the invokation will succeed despite the fact that File
and Door
are different types.
Basic Syntax
Hello World!
As mentioned above, Ruby is a script language. It is thus possible to list some statements in a file (conventionally having the extension .rb) and then execute the statements in it using the Ruby interpreter ruby
. The traditional Hello World program can look like this in Ruby:
puts "Hello, world."
Listing 1: hello_world.rb. Hello World in Ruby.
The program can then be run by executing ruby hello_world.rb
in the shell. Alternatively the Ruby interactive interpreter, irb
, could be started and the program entered into it:
lars@lars-lilla:~$ irb
irb(main):001:0> puts "Hello, world."
Hello, world.
=> nil
irb(main):002:0>
Listing 2: Hello World in irb.
Variables
Variable usage in Ruby is very straight forward. It does not have any special keyword (e.g. def
) or type in its declaralation. Simple variable definition looks like this: i = 0
. Couldn't be any simpler, eh?
The convention is that variables start with a lowercase letter. The words in Multi-word variable names should be separated by underscores: important_value
. Apparantly, this is called snake_case.
No special characters should be used when accessing a variable, i.e. you don't have to prepend a $
to the name of the variable when accessing its value. I.e
a = 1
b = 2
c = a + b
puts "#{c}"
Listing 3: Accessing variable values.
In the example above, we also see the syntax for embedding the value of a variable in a string: #{variable}
.
Contants
Constants are defined by using SCREAMING_SNAKE_CASE
. E.g IMPORTANT_CONSTANT = 3
.
Note that the only difference to tell the difference between a variable and a constat is to look at their names. All caps => constant, not all caps => variable.
If one tries to reassign a constant, irb
complains but will perform the reassigment,
irb(main):001:0> A_CONST=42
=> 42
irb(main):002:0> A_CONST=3
(irb):2: warning: already initialized constant A_CONST
(irb):1: warning: previous definition of A_CONST was here
=> 3
irb(main):003:0> puts A_CONST
3
=> nil
Listing 4: Defining and reassigning a constant.
Arrays
Arrays are defined and accessed using brackets:
an_array = []
an_array[0] = 'Hej'
an_array[1] = 42
puts an_array[0]
puts an_array[1]
puts an_array[2]
...
Hej
42
Listing 5. Array assignment and access
Note that no error message were given when an element that hasn't been assigned was accessed. Such elements evaluate to nil
which makes put
print an empty row.
The Array
class, that arrays are instances of, contain many methods. For example, an element may be appended to an array using the <<
method:
another_array = ['This is the item at index 0', 'This is the item at index 1.']
another_array << 'This is the item at index 2.'
puts another_array[0]
puts another_array[1]
puts another_array[2]
...
This is the item at index 0
This is the item at index 1.
This is the item at index 2.
Listing 6. Initializing an array and appending to it.
See its section in the official Ruby API documentation [1].
If clauses
The syntax of an if-clause is the following:
a = 42
if a == 20
puts "Equal to 20"
elsif a < 10
puts "Less than 10"
else
puts "Not 20 and not less than 10."
end
Listing 7: Basic structure of an if-clause.
The logical operators come in two flavours: 'textual' and 'symbolic'. The textual variants are: and
, or
and not
. The variants using symbols are &&
, ||
and !
. The textual operators have lower precedence than the symbolic ones.
The comparison operators are: ==
, !=
, <
, >
, <=
and >=
.
if
:s can also be used as statement modifiers:
b = 3
puts "b is 2" if b == 2
puts "b is 3" if b == 3
...
b is 3
Listing 8: if as statement modifier.
Loops
Ruby supports a multitude of loops. The usual for
and while
loops of course, but also the little more uncommon until
.
A while loop looks like this:
i = 1
while i < 3
puts i
i += 1
end
Listing 9: While loop.
If one wishes, a do
can be inserted after the conditional:
i = 1
while i < 3 do
puts "Using do"
i += 1
end
Listing 10: While loop with do keyword included.
Note that do
do need to be locaded on the same line as the conditional. It is an error to place it on the next line.
while
can also be used as a modifier:
i=1
begin
puts "Inside while modifier loop; "
i += 1
end while i < 3
Listing 11: While used as modifier.
until
supports the same syntax, both as a regular loop:
i = 1
until i == 3
puts "Trying out until"
i += 1
end
Listing 12: until loop.
and as a modifier loop:
i = 1
begin
puts "Until as modifier"
i += 1
end until i == 3
Listing 13: until modifier loop.
for
loops can be used to loop over ranges
:
for i in 1..3
puts i
end
Listing 14: Simple for loop.
Note that the end index in the range is inclusive.
In additions to theese loop constructs, Ruby also supports more functional loop constructs like each
. The numeric classes also contain methods that can be used to create loops.
Ruby includes some statements to alter the flow of the loops. Theses are:
break
- breaks out of the (inner most) loopnext
- continues with the next iteration, if any, of the loop.redo
- restarts same loop iteration again without trying the loop condition.
An example of controlling the flow of a loop using redo
:
i = 0
redone = false
while i < 3
puts "i = #{i}"
if i == 2 and redone == false
redone = true
redo
end
i += 1
end
...
i = 0
i = 1
i = 2
i = 2
Listing 15: Example of the redo statement.
Functions
A function is defined using the def
key word. Is is ended by the end
keyword. A simple function can look like this:
def say_hello()
puts "Hello"
end
Listing 16: Simple method.
A function may be called either with or without parentheses around the argument list:
say_hello()
say_hello
...
Hello
Hello
Listing 17: Parentheses are optional.
The order of the function definitions in a file is insignificant.
def b
puts 'B'
a()
end
def a
puts 'A'
end
b()
...
B
A
Listing 18: Order of function definitions is insignificant.
A function may however not be called before it is defined:
c()
def c
puts 'C'
end
...
function_call_order.rb:1:in `': undefined method `c' for main:Object (NoMethodError)
Listing 19: Function called before it is defined.
Classes
A simple class can be constructed like this:
class AClass
attr_accessor :anInstanceVariable, :anotherInstanceVariable
@@A_CLASS_VARIABLE = 42
# This is a constructor.
def initialize(aValue)
@anInstanceVariable = "This is an instance variable"
@anotherInstanceVariable = aValue
end
end
Listing 20: Example of a class.
Some things about classes:
- A class is defined by the
class
keyword. - If the class inherits from another class the syntax is like this
ASubclass < ASuperclass
. - Methods named
initialze
are constructors. - Instance variables have names starting with a single @. E.g.
@thisIsAnInstanceVariable
- Class variables have names starting with two @-signs:
@@thisIsAClassVariable
A nice feature of Ruby's is that getters- and/or setters can be generated automatically. To use this feature, one lists the variables for which such methods should be used after one of the following keywords: attr_accessor
, attr_reader
or attr_writter
. The difference between them is that attr_reader
generates a getter, attr_writter
generates a setterer and attr_accessor
generates both a getter and a setter.
What about that colon sign before the variable names in the attr_accessor
list? That colon signifies a symbol. More about symbols in the section below.
An example program using the class we defined above can look like this:
require_relative 'a_class'
a_class = AClass.new("This is the value")
puts a_class.anInstanceVariable()
puts a_class.anotherInstanceVariable()
a_class.anotherInstanceVariable = "Changed value."
puts a_class.anotherInstanceVariable()
...
This is an instance variable
This is the value
Changed value.
Listing 21: Program using the example class.
The require_relative
statements makes it possible to access the class. The relative part makes the search for the class relative to the directory of the file the require_relative
is located in.
Features
This section contain stuff that are...how shall I put it..somewhat special for Ruby as a language. They are not unique, I don't think very many programming language features exist that are included in only one language, but they are not mainstream either.
Symbols
A symbol consists of a colon followed by an unquoted string (most often) or a quoted string (if the string contains white space).
What then is a symbol? Well, that is a rather difficult question to answer. I've googled a fair bit, and the best answer I've come up with is this: a symbol is an immutable thing that has both a string representation and an integer representation. The string representation (retrieved by calling to_s
) is equal to the name of the symbol (minus the colon). The integer representation is fetched using the to_i
method.
A sole instance of a symbol is referenced whenever that symbol is used in a program. This is the main benefit of symbols, I think.
See [2] for a nice and informative discussion about symbols.
Blocks
Blocks are lambda expressions that can be passed to a function by adding a piece of code enclosed in either curly braces or do
-end
after the regular method parameters when invoking the function. Only one block can be passed to a method. The convention is that curly braces are used for one-liner blocks, and do
-end
for all other blocks.
A block can take zero or more parameters. Those are placed in a comma delimited list between |:s right after the left curly brace or do
.
An example:
an_array = [2,3,5,7,11]
an_array.each {|num| puts num}
...
2
3
5
7
11
Listing 22: Block taking parameters.
To call a block that has been passed into a function, the yield
keyword is used:
def method_receiving_a_block(regularParam)
puts "The regular (non-block param is #{regularParam})"
puts "About to call the block..."
yield
puts "...after the block"
end
method_receiving_a_block("this is the regular param ") {puts "Inside the block!"}
...
The regular (non-block param is this is the regular param )
About to call the block...
Inside the block!
...after the block
Listing 23: Calling block from the function receiving it.
In the function method_receiving_a_block
above, we see that the the block we passed into the function is called when the yield
statement is invoked.
If one wishes to pass parameters when invoking a block, those params can be specified when calling yield
:
def method_receiving_a_block(regularParam)
puts "The regular (non-block param is #{regularParam})"
puts "About to call the block..."
yield "Param to the block"
puts "...after the block"
end
method_receiving_a_block("this is the regular param") do |param|
puts "Inside the block!"
puts "The parameter to the block is: #{param}"
end
...
The regular (non-block param is this is the regular param)
About to call the block...
Inside the block!
The parameter to the block is: Param to the block
...after the block
Listing 24: Calling block with parameters from the function receiving the block.
Modules
A module in Ruby is a way to group classes, functions and constants so that they can be used as a unit. It provides a namespace that can be used to avoid name clashes. A module is imorted using the require
or require_relative
statements. The difference between require
and require_relative
is that require_relative
start searching from the current directory while require
searches the LOAD_PATH
.
An example of a simple module (defined in file a_module.rb)
module AModule
A_CONSTANT = 42
def AModule.a_method()
puts "Inside a_method"
end
end
Listing 25: An example of a module.
As can be seen in the example above, a function that is defined in a module should have the module name and a dot prepended to its name.
The module above can be used like this:
require_relative 'a_module'
puts "a_module.A_CONSTANT has value: #{AModule::A_CONSTANT}"
puts "Invoking the function a_method... "
AModule.a_method
puts "...back in the main program."
...
a_module.A_CONSTANT has value: 42
Invoking the function a_method...
Inside a_method
...back in the main program.
Listing 26: Example of using a module.
When using a constant defined in a module, the module name and a double colon should be prepented to the function name. Likewise, when invoking a funktion in a module, the name of the module must be specified.
A module can be included in a class. The module is mixed into the class. Let's see how that works.
Mixins
A class in Ruby can inherit from only one superclass, i.e. Ruby supports only single inheritance. Something that resembles multiple inheritance can be emulated by mixing in one or more modules in a class. The constants and functions from those modules can then be invoked using an instance of the class. Consider this module:
module CModule
def c_method()
puts "Inside c_method"
end
end
Listing 27: Module meant to be mixed into a class.
It can be mixed into a class (using the include
statement) like this:
class BClass
require_relative 'c_module'
include CModule
def bMethod
puts "Inside the bMethod"
end
end
Listing 28: Class with a module mixed in.
We can now call functions from the CModule
using a reference to the BClass
:
require_relative 'b_class'
b = BClass.new
b.bMethod
b.c_method
...
Inside the bMethod
Inside c_method
Listing 29: Using the mixed in function.
Note that when the module is used as a mixin, the module name should not be prepended to the name of the functions in the module.
A class can more than one module mixed in.
So what's the difference between mixing in a module and using multiple inheritance? Here's my view: The intention of mixins is that a module that is mixed in should contain some well-defined piece of functionality that other classes might want to expose. Instead of a class A
inheriting from a class B
just to gain access to the functionality it exposes, and thereby saying that the inheriting class is a B
, mixing in a module M
exposes the same funktionality as B
without adding the is-a
relationship. A module can be mixed into two (or more) totally unrelated classes that only have in common that they want to expose the functionality of the module. In summary: exposing a shared piece of functionality without adding the is-a
-relationship.
Metaprogramming
Metaprogramming is the fine art of writing programs that manipulates itself and/or other programs. In Ruby, one way of implementing metaprogramming is by overriding the method_missig
method in a class. When doing that, all method calls to non-existing methods in the class will be routed to method_missing
.
An example:
class CatchAllClass
def method_missing(name, *args)
if name.to_s == "secretMethod"
puts "You found the hidden method!"
else
puts "That does not compute. Method: '#{name}' does not exist"
end
end
end
Listing 30: Example of implementation of method_missing() in a class.
The class above can be used like this:
require_relative 'catch_all_class'
c = CatchAllClass.new
c.doesThisWork?
c.secretMethod
...
That does not compute. Method: 'doesThisWork?' does not exist
You found the hidden method!
Listing 31: Using the class that implements method_missing() .
This is, of course, a toy example. In s more serious program, we could use method_missing
to for example handle queries to a database where the name of the called method determines which parameters to use in the selection criteria of the query. This is e.g. what the persistency mechanism in Ruby on Rails does.
There are also other metaprogramming constructs in Ruby. Among other things, methods can be added to classes at runtime using define_method
.
Gotchas
Here are some things that tricked me while writing some example programs in Ruby:
Precedence of logical operators and the ternary operator
When using logical operators in conjunction with the ternary operator, my assumption was that ?
had lower precedence than and
and or
, but such is not the case in Ruby. An example:
a = true
b = true
s = a and b ? "First" : "Second "
puts "#{s} branch chosen"
s = (a and b) ? "First" : "Second "
puts "#{s} branch chosen"
...
true branch chosen
First branch chosen
Listing 32: Ternary operator precedence.
As the example above shows, the logical operator and
have lower precedence that ?
. This is not the case in neither Java nor Python, which tricked me.
End of interval is included when looping over ranges
From Python, I'm used to the end index being excluded when looping over a range of values. Not so in Ruby. Here the end index is included.
for i in 1..5
puts i
end
...
1
2
3
4
5
Listing 33: Ending element of a range is included..
FizzBuzz in Ruby
The FizzBuzz kata is the simplest kata that I've tried out. For those of you that haven't come across it, the task is to print the numbers from 1 to 100, but when a number is divisable by 3 and/or 5 a word should be printed instead of the number. When a number is divisable by 3, 'Fizz' should be printed and when a number is divisable by 3, 'Buzz' should be printed. If the number is divisable by both 3 and 5 the word 'FizzBuzz' should be displayed.
This my implementation of the FizzBuzz kata in Ruby:
1.upto(100) do |x|
print "Fizz" if x % 3 == 0
print "Buzz" if x % 5 == 0
print x if x % 3 != 0 and x % 5 != 0
puts
end
Listing 34: FizzBuzz in Ruby.
What I like about Ruby
Ruby is a nice language which I like to program in. It has a nice feeling. More specifically, these are some of the things I like with Ruby:
- The language is compact, but not too compact. Since I last visted Ruby, it seems that the
do
starting the body of a function has been made optional. This is nice I think. Of course, this is a very minor detail, but still. Now, if they can get rid of theend
as well... - blocks - nice implementation of lambdas/closures.
- inline form of
if
- puts "Hello!" if name == "Lars" attr_accessor
and its siblings. Nice way to avoid writing the getters and setters that often make up a not unsignificant amount of the code in a Java program.
What I don't like about Ruby
Here are some of the things I don't like about Ruby:
- That the parentheses are optional when defining a function. I like it when it is very clear what kind of language construct the piece of code I'm looking at belongs to. I don't like when I'm forced to stop and think about if it is a function definition, a function call, a lambda expression or something else. My opinion is that skipping the parentheses around the parameter list when defining a function is making the code less clear, and therefore should be avoided.
- That the parentheses are optional when calling a function. For the same reason as stated in the bullet above.
- That the
return
statement of a function is optional even if the function is designed to return a value. If ommitted, the value of the last expression is returned. Not including an explicit return statement is also making the code less clean. An exception might be a single line getter method. But the number of characters saved when writingfoo
instead ofreturn foo
isn't very many, so it might be better just to always usereturn
when returning a value from a function.
Onwards, upwards
That was that regarding Ruby. I really like the language and hope that I will get to program in it professionally some time. I think that I've learned quite a bit about the basics of the language. To be honest I think that I learnt more from reasearching stuff for writing this blog post than from reading the Seven languages in seven weeks book. But it also took alot longer to write the blog post than to read the Ruby chapter in the book and doing the exercises in it, so that might be as it should.
Now it's time to turn to the next language in line: Io. Somehow I think it will be...very different. I sure that it will be fun to learn something about it. I'd better get started...
Links
[1] Ruby API documentation - Arrays[2] The Ruby Newbie Guide to Symbols
[3] Ruby API documentation