Symbols are fantastic uses of singletons and literals and are extremely useful. They are written like
:symbol. Think of them as interned strings. They are singletons in the sense that two symbols, with the same name, are the same object. Yes, they are objects. Using them, instead of strings, except where strings make more sense, of course, saves both time and memory space.
Common uses for them are for the key names in hashes and to set options in objects or entire programs.
10. Gems and Bundler
RubyGems ships with Ruby and is the easy way to download libraries, in Ruby, these are called gems. On the command line it is
gem install <gem name>. It can also download specific versions. The
gem command can be used to update and delete them.
The most useful gem in existence is bundler. One issue with gems is what happens when multiple programs use different gem versions. Bundler comes to the rescue. All it needs from the programmer is a simple file called Gemfile. Gems can be grouped as needed like ‘development,’ ‘production,’ or ‘testing’. You can tell bundler the gem name, what version or version range is acceptable, and if it is in a nonstandard place, the URL to the gem and it manages it flawlessly. Bundler will manage dependencies of gems you do include without any extra work.
It is a Ruby file. There is no convoluted XML or any of that insanity. It is a nice little tool that makes Ruby even nicer to use even though it is strictly not part of the language, its runtime, or even required.
Here is a simple example. This is the Gemfile for my Octopress install, the program that generates the webpages for this site, which is all done on my desktop. Only HTML, CSS, and JS files are uploaded to my server.
None of these reasons are unique to Ruby, but few languages can claim this complete list outside of Lisp languages and those heavily influenced by Ruby. All of these features and more add up to a rather large language that feels small, produces an extremely low line of code count, is very powerful, yet is easy to learn.
One problem is performance. It is being addressed. Some of the work is out with the last version, 2.7.0. More improvements are being done on the run-up to Ruby 3.0.
One thing to remember is that a language is neither fast nor slow. It is simply a specification. Sure, decisions in that specification can influence the performance of the implementations.
The C language is considered very fast, yet it is possible to write poorly performing code if things like spatial and temporal locality are ignored by the programmer, as an example.
Code written in C does not have to be compiled to be able to run. There exist C interpreters that obviously will not perform as well. I could write a C interpreter, and I promise you that it will have very poor performance, but it will still be C.
Performance is a complex subject with all sorts of factors that need to be measured, analyzed, and considered. Ruby is fast enough for what it excels at. If a program spends the majority of its time waiting for other things, such as data coming from a database, or waiting for requests, spending a lot of time optimizing your code or using a faster language raw execution speeds matter less. The work required to write faster code when it still has to wait isn’t as useful or productive as one might think.
One language that is interesting and based on Ruby is Crystal, which is very Ruby-like but natively compiled.
It is faster, but because it is statically compiled, it is lacking many of the meta-programming and reflection features of Ruby. Changing and adding code at runtime is difficult to pull off in a natively compiled language and keep performance high. There are implementations of Common Lisp that seem to do well at it, so I have hopes for this project. It doesn’t seem to be as far along as Rust but it looks like it is far less complex syntax-wise. I am not sure it is production-ready yet, but it’s marching along. It does not follow Ruby development and never matches any specific Ruby version - AKA it isn’t a Ruby implementation - but its Ruby-inspired syntax makes it nicer to write, at least. I haven’t used it for anything more than playing around, but it has potential.
Elixir also is heavily influenced by Ruby’s syntax. The syntax of Elixir might look a bit like Ruby, but being a functional language, it is more stringent and therefore less flexible and slightly more verbose, which is a good and bad thing in my estimation. Elixir is built on Erlang/OTP and is useful for some of the same things that Ruby is good at doing. Elixir is extremely capable in many other classes of programs for which Ruby is not suitable. Erlang/OTP is a language and platform that provides soft real-time and concurrency at truly massive scales, two things that Ruby could not handle. Although Ruby can scale up better than many believe, it simply can not compete with Erlang on this front, and few languages can without a herculean effort.
No matter how much I like a language, and I enjoy Ruby - I took to it faster than any other language I had learned up to that point, which was a fair number - there are things I dislike that drive me up the wall sometimes.
Many of the features I love require a bit of discipline to keep everything maintainable. That is not a negative, just an annoyance. Professional tools don’t necessarily need to be difficult to use, but expecting a certain level of competence from programmers isn’t asking too much. Still, the language could enforce a few more things without losing much flexibility, if at all.
Keeping track of methods used in other classes and mixins takes a little extra work. If you are not careful, it can be a little difficult to follow object hierarchies and especially what method is in what superclass or module. It is not overly complicated but something else to keep stored in the old noggin while working. Making things obvious in comments, documentation, and directory structure fixes most of this but not overusing these features is the best defense.
Sometimes language features creep in that seem pointless and redundant. One such example is Refinements, which was introduced way back with Ruby 2.0 if I remember correctly. This feature added additional code for existing functionality: the ability to add methods to existing classes during runtime. Ruby has always been able to do that, but refinements had a few new odd restrictions. It helped a little bit with keeping code clean and obvious but it is not like that was not possible before.
This is getting unreasonably long, so maybe I will write a ‘why I hate Ruby’ post where I can get it all out. I don’t whine and complain enough.
A strength of Ruby is that there are many, many ways to skin the same cat. At some point, new cat skinning methods typically don’t add much of anything at all. Several other pointless and redundant features have been introduced since Refinements. It is like too many developers think that features need to constantly be added, and code that is not updated gets moldy or something.
Especially with all of the low-hanging performance problems, addressing it is a fairly new project. Adding features of dubious value is not going to progress a language that is already quite advanced and seems to me like a waste of effort.
The good, great, bad, and ugly all results in a language where great complexity can be hidden in a fraction of the lines of code compared to languages such as Java and C++ and even Python - the difference in lines of code is not all that great compared to Python - and is extremely easy to read with just a little understanding of Ruby, especially its object model. Things that would take a ton of complex code in some languages can be downright trivial in Ruby. Many design patterns vanish, perhaps become redundant is a better descriptor, in Ruby because of the way it is designed.
There are many great real-world examples of how flexible Ruby can be such as ActiveRecord or RSpec. The implementations can get complex, mainly because of all the edge cases in these problem domains. The interfaces are simple, clean, powerful, and can easily bend to however the programmer needs.
The following example is not a great real-world example. To keep it simple, I am using an awesomely fun example to show off the runtime flexibility of Ruby. Make no mistake, it is also a stupid and pointless example, but it is easy to understand and even easier to write.
The following code is a simple example of an accumulator. It starts at a value and adds what number you give it. If you start with 0 and then pass a 5 in it and then pass -20, the value will be -15. Super complex, right?
The example starts with a function that creates the accumulator lambda, and then values are passed in. To get the current value, pass in 0, which gets annoying. To get over that, and since lambda is an object, we will give it more functionality. To dispel the notion that craziness rules in Ruby, the last example is a plain old class version of an accumulator that works exactly like the souped-up lambda version.
There are more complex versions of an accumulator that work with both arrays and hashes and the method
inject(), but we are keeping things super simple today.
Simple Accumulator Example
Using call or  is kind of clunky so let’s make it work a little better.
Add more useful functionality to the lambda
Just to poke at Java a little bit more, here is an AccumulatorLambdaFactory, Ruby style so it won’t be 10,000 lines long. :)
Put it together
A more straightforward and sane OOP approach
Hopefully, someday soon, I will upload an article demonstrating a better and fuller example of Ruby, perhaps as a Domain Specific Language(DSL) such as my little language to teach concurrent programming concepts. A multi-part explanation of the object model is on my list of things to do. I wrote this entire mess off the top of my head in one sitting - and it shows! - I will take a deeper dive into different parts of this and other languages and use better thought-out examples that might be more obviously useful in the real world.
Everything here can be adapted for a much better use case. In my defense, messing around in a language doing goofy things can help you learn the language while having fun. You can end up doing something surprising, for good and ill, and bump into edge cases.
It’s all fun and games until you learn the hard way about fork bombs. That was a fun discovery when learning concurrent programming in C. I was curious if Ruby could save me from ruining everything with fork() and tried it out just now. It, not surprisingly, locked up my VM, so that is good for a laugh.
It is not as catastrophic but I did something similar blowing up stacks learning recursion.
If you aren’t breaking something, you aren’t learning. *sigh* I miss being a student.
I did not realize this was so long. I really did write it in one sitting. That says something about me, not sure what that is.