Computer science is one of our main interest. Each of us deals with it
in a different manner but every one of us needs to be aware of all the
major events that bring some sort of new feelings in the game and that
bring with them some new and smarter ways of solving our everyday problems.
In the programming field one of these new tool is Ruby. It is a brand
new language and it goes like this.
Fashion or not fashion ?
Programming and computer science in general deal with fashion and hype. It deals with speed and "brand-new-and-terrifying-kicking-ass-products" everyday and most of them end up totally forgotten in your company garbage near your last bottles of coke.
It is part of our job to
sort all this mess and pick up reliable and well-done things that would
enhance our productivity and allow us to produce better software.
This is part of our task to keep our eyes opened on the world and
on its new products and technologies. Ruby is part of those
new fancy stuff and I think it really deserves some time to get
interested in it.
What is that damned stuff called Ruby ?
And the answer is : a new interpreted python-like programming language. As python, it is an Object Oriented (OO) one and includes a lot of interesting features that you could find in some other languages like Smalltalk, C++, Java, etc ...
It was created by a japanese guy named Yukihiro Matsumoto alias "Matz" and first released in 1993. He gave birth to this in order to propose his own OO alternative to Perl and Python programming. Perl is definitely not an OO language and Python did not propose an alternative that was efficient and clear enough for him.
He wanted to have a fully OO programming language with heavy dynamic properties and a very clear syntax. And when I mean dynamic I do mean it. He added some very interesting features such as reflexivity, on-the-fly class redefinitions, run-time variable type determination (dynamically typed), garbage collector, etc ... capabilities.
And one of the main interesting point that makes this new programming language "fun" (and productivity-compliant) is its very intuitive and nice syntax. Along with this syntax, Matz wanted all this language to rely on one simple rule "The Principle Of Least Surprise". It really means what it says, the language is designed (and succeeds most of the time in this) so that there is no relative distance between an idea or an algorithm -that you want to implement- and the way to express it in terms of Ruby language. Everything is very natural or tries to be.
And one other important goal was that
he wanted Ruby programming to be "fun" and pleasant which I think
he achieved quite well.
Let's be practical.
Here comes the time to see what this new stuff have in its guts.
There is way too much things to say about it and I cannot be exhaustive
in this simple enumeration.
The main point to understand, in this language, is that everything is
an object. I really insists on the EVERYTHING stuff. For example, every
litteral number or string is an object to which you can apply all
its interface methods :
A litteral string is transformed to an integer (you apply the method to_i with no parameters to the litteral string
"1976") :
"1976".to_i()
A litteral number is transformed to a string (you apply the method to_s with no parameters to the litteral number
3) :
3.to_s()
An interesting point, is that the Ruby interpreter allows you
drop some parts of an expression when there is no ambiguity in
your demands. For example, in the two upper Ruby lines I could
have dropped the '()' part because there is obviously no need to
keep them since there is no parameters in our method call.
This fully-OO stuff can be applied to anything and in particular
to any of the build-in types that are in fact classes. Like Python,
it supports : Arrays, Hashes (associative arrays), Small numbers
, Big numbers, Boolean, Regural expressions, IO objects, Strings,
Build-in Threads, Symbols, ... Moreover, Ruby comes with a large Web library that features CGI, FTP,
Telnet, TCP, UDP, ... classes.
To be a little bit more pragmatic and to show another example
of syntax, Ruby supports a very interesting
and helpfull feature called blocks. To make things simple, blocks
are portions of code that comes between a 'do' .. 'end' statement
or between '{' and '}'. You can manipulate these blocks as you want, they are litteraly portions of script that you can plug everywhere.
You can use these blocks, for example, as method parameters :
6.upto(10) { |i|
print i
}
Hey, let's split this stuff a little bit to understand how it works.
First, in Ruby there is no need of semi-colon, like in C for exemple.
Semi-colons are only used when you put multiple instructions in one
single line.
Second, you have the '6.upto(10)' part that applies the upto() method to
the '6' integer class object. As we would expect, the upto() method parameter (10 in this case)
indicates the upper limit of the desired count action.
Here is another possible syntax that would make things a little
bit clearer regarding the purpose of this piece of code :
6.upto 10 do |i| print i endOne important thing that Ruby allows you to do is to put away the parenthesis in a method's parameters list if there is no ambiguity. So this piece of code is still valid and might now appear a little bit more clear : you just have to read it !!
"Count from 6 to 10 and, for each count, do this". Here is what it means. Simply take some time to compare the litteral expression of our desire and its Ruby translation. Pretty clean no ?
So now comes the problem of the '|i|' expression. This corresponds to one block parameter. In this case, the method upto() directly translates the programmer's will to count from 6 to 10. Usually, when you write such lines, it probably means that you want to perform an action a certain number of times. The block, here, is a way to direclty give the upto() method the portion of code that you want to execute. The upto() method, internally, calls your block for every loop count.
The problem is that you might want to have access to the loop count.
You can get this parameter for each call to your block is through this '|i|'.
You put every block parameters between '|' , at the beginning of your block,
separating each parameter by a comma.
It tells Ruby "Well, if you have any parameters for my block I want
it to be called 'i' in every loop". If you do not need this parameter,
well, ... just drop it ! Why bother, the language takes care for you.
A taste of Ruby classes.
You can of course build you own classes with this Ruby stuff and let's give it a very quick try. Here is one simple ConfigFileParser class that is used to simply extract some relevant informations from a text file. This text file should be formatted as lines of "[InitializationElementName] = value" expressions. Here is a very simple and not error-safe Ruby script I wrote for this example (the keywords are in blue) :
class ConfigFileParser
def initialize( filename )
@filename = filename
File.open(@filename, "r"){ |file|
file.each_line { |line|
key, value = parseLine(line)
if key && value && block_given?
yield key, value
end
}
}
end
def parseLine(line)
if line =~ /^\[([^\[\]]*)\]\s*=\s*(.*)/
return $1, $2
end
return nil, nil
end
end
ConfigFileParser.new("test.txt") { |key, value| puts "key: #{key}, value = #{value}" }
Hey, WTF is that stuff ! Yes I admit that it is quite rough as an introduction to Ruby
classes/syntax but it contains a good amount of interesting features.
class MyClass endHere it is ! The keywords class and end respectively open and close a class definition scope. The name of a class must start with an upper case character as the Ruby interpreter parser uses that kind of name convention to interpret the source file. It uses in fact a couple of other conventions related to method names, constant names, etc ... Do not get frightened by this, as these conventions are rather light and you get very quickly used to them.
We define instance methods by using the keyword def and by using the keyword end when the definition finishes.
We define instance variables by adding a '@' sign in front of its name.
So in the previous source, we define an instance variable (@filename) and an instance method (parseLine) in this class. The class is structured so that we immedialty try to open and parse the file in the constructor. The built-in class File is used here. Once the file is opened, the instance method parseLine() is called for each file line and tried to extract the key element and its value from it.
An important point here is that we use blocks (as we have seen before) with the class method open() (i.e. static one) method of the File class. This method accepts blocks. It tries to open the current file, and simply calls the block with the opened file as a parameter if the open action succeeded. A very important point, here, is that when you get out of the block, the open method takes care of closing the file for you.
Before leaving this example aside, I would like to explain two particular points. The first one is the use of the return keyword. return has the same meaning than in C/C++, it simply returns the given value. As Ruby is quite versatile, it allows you to omit the use of return and then, just returns the value of the last expression evaluated in the method body. But here, we use a particular construction of the keyword return that features parallel assignement. In this particular case, I want my parseLine() instance method to return both the key and its value (which is extracted from a line with a very simple regular expression). Ruby allows me to return both of them separated by a comma. In this case, following the Principle Of Least Surprise, I am able to get back this value simply using the same contruction :
key, value = parseLine(line)If there is too much receiver variables Ruby fills the extra ones by a nil value. The second point on which I wanted to come back is the fact that we allow the user to add a block to the constructors parameters list. The line :
ConfigFileParser.new("test.txt") { |key, value| print "key: #{key}, value = #{value}\n" }
means that we instanciate a new ConfigFileParser class with a first argument "test.txt" and a second
argument of type block that takes two possible parameters. This block can be called by the constructor
at any moment and, if so, receives two arguments key and value. It just prints the values of its arguments.
The constructor can check, at any moment, the existence of a block parameter by calling the
method block_given?() that returns true is a block have been given to the contructor. If so,
it can call this block by calling the yield method with the appropriate parameters !!
So here it is for this example. I cannot go further on this because this text is not meant
to be a full introduction just a simple "curiosity awakener" one.
Advanced Ruby.
Ruby is a very dynamic language. You can redefine everything at runtime even your own source code ! You can easily get an enormous amount of informations on the running context that would allow to do crazy things like runtime auto-debugging and correction, etc ...
You have full ability to inspect the current context and to get informations about all the objects currently existing, about your own object class hierarchy, about the content of objects (methods and variables) that can of course be modified (runtime method redefinition), etc ...
You have also the ability to trace your program call-stack at runtime. You then have access to the information of who is calling what.
Ruby also supports built-in marshalling (object serialization). You find this technique in more and more environments now (Windows ATL for example adds supports COM objects marshalling).
I cannot talk about every Ruby features there is plenty others but I am pretty sure that you get the idea.
But i do not resist to give you a slight example of how Ruby could let you treat and solve a problem
in a clever and interesting way. Let's say that you have some sort of a server that is able
to answer correctly to a certain amount of requests. Each request takes the shape of one or several words
that summarize each action. For example (very stupid one), you may want to ask the server about the current time.
In that case, the words "Give time" might be sent to the server for example. A simple implementation of
the message processing on the server side in C, might deal with function pointers associated with strings
together in a structure for example or maybe, in C++, some sort of STL std::map associating words to static methods
perhaps (bad solution though).
In Ruby a solution description might look like this one :
1. Format the string in a more compact way (replace the ' ' by '_', etc ... ), 2. Simply use the string as a method name and try to call it, 3. If this method does not exists, Ruby tell it to us and we do what is supposed to be done in such cases,In a source file, it would look like (the request string is 'requestStr' here):
# remove all annoying characters (non alphanumeric ones plus '_') so that the string # might be considered as a valid method name requestStr.gsub!( /[^\w]/, '_' ) # try to call the method corresponding to the requestStr name (loosely) self.send(requestStr)If this method does not exist, Ruby tries to find and call a method of our class named 'method_missing'. So we just have to implement such a method to handle invalid requests :
def method_missing(methodId)
puts "Invalid call to method : #{methodId.id2name}"
# we may here raise an exception to inform the system/class user that something went wrong
raise "Invalid call to method : #{methodId.id2name}"
end
I really hope that i haven't disgusted you with such rough explanations of this kind of behaviour. Once again,
i hope you get the idea.
The Ruby interpreter is written in plain C language and is an open source project that is currently in its 1.6.6 version. It is highly
portable and therefore exists on a great number of plateforms : UNIX, DOS, Windows 95/98/NT, Mac, BeOS, OS/2, etc.
I have even compiled and tried it under QNX 6.0 !
You can easily extend Ruby using a C API. You can therefore add your own custom support for any
of your tools, project, specific points, ...
Usefull for what ?
Ruby is not a fancy, useless new fashion that floats around here. It is really worth spending time learning it. It can be used in a great number of areas.
I personnaly use it as a prototyping language to enhance my productivity and test some of my ideas. I also use it as many areas where C/C++ are not required.
In more graphics related words, Ruby supports a lot of libraries that extend its features in this area. It has an OpenGL library, and SDL one, some very usefull image reader ones, ... If you add the support of many GUI frameworks such as GTK+, Tk, FOX, win32 SDK ... that allows you to really speed up your application development processus, you have a perfect tool to build very interesting graphic related applications (various world editors, renderers ...).
Ruby can do almost everything and you will be able to produce an interesting result in a very few time. It can also be used as a glue between different and heterogeneous parts of a project.
And one other important, but less obvious reason why Ruby is worth learning, is that it is a wonderful educational tool. It features very advanced stuffs to which you have a very simple and intuitive access. It enables you to understand and experiment a lot of important OO and programming language related points.
There is no barrier between you and the language, you can expriment and implement with an incredible ease very complicated/strange/crazy stuff with all this dynamic features, there is no obstacle that would prevent you from easily come up to a direct implementation of your ideas. The language is not running against you but it tries to take any shape you might imagine.
Despite such compliments towards Ruby, we must not forget that every language has its strengths and weaknesses. Our task is to know about both of them to be able to choose the most appropriate one in front of a problem.
First, Ruby is interpreted and does not appear (in this current interpreter implementation) to be as fast as Python : (http://www.bagley.org/~doug/shootout/). Even if the next Ruby interpreter (re-written from scratch and called Rite) seems to be very very promissing, this is a fact.
Moreover, even if Ruby is quite old (1993 for the first release), it is a quite young language outside Japan. The community is growing everyday and more and more people do talk about it but the fact is that it does not have the impact (in the Web for example) of Python or Perl for example.
Ruby and Python are direct concurrent languages and one say that as Python is "more" advanced in terms of libraries/use/managability it is not worth looking at Ruby.
Ruby lacks some features that have personnally annoyed me : lack of class destructors or any mechanism of this kind (you can use block arguments but it is really not a clever and usable solution in my opinion in many cases), no cool GUI framework that would come with a good (and english) documentation, no current easy and satisfying possibility to build an executable from a Ruby script (it simply prevents Ruby script from being used in professional company packages; which company would accept that its clients would have to install Ruby to run part of a software, for me all the necessary script files should make a simple exe file), ...
Moreover, we must not forget
that this kind of Very High Level programming language ('High' in terms of relative distance to the
computer) cannot be appropriately used in any cases and does not support the comparison with
languages like C or C++ in terms of system programming, for example.
Links.
Ruby main page site: http://www.ruby-lang.org/en/ Ruby application/library archives list: http://www.ruby-lang.org/en/raa.html A Ruby portal: http://www.rubycentral.com/ An other Ruby portal: http://www.ale.cx/mine/ The first book on Ruby online (very good): http://www.rubycentral.com/book/index.html A Windows Ruby installer: http://www.pragmaticprogrammer.com/ruby/downloads/ruby-install.html A Ruby page: http://www.rubygarden.org/ The Ruby newsgroups archive (very very interesting): http://www.ruby-talk.org You can have access to the very dynamic newsgroup: comp.lang.ruby.Conclusion.
I hope that this little introduction was good enough to give you the desire to look a little bit at this language. Even if you do not choose to go further and deeper into it, it really contains some interesting ideas that are worth speding at least a couple of weeks on it.
If you want to give me feedback on this : wiss1976@yahoo.fr.
Happy coding.
| Alexandre Abreu aka Wiss. |