Meandering Musings

Everything not fit to publish

Why I Like Ruby

| Comments

For some reason, Ruby is often maligned, usually by people with not the strongest understanding of the language. They see it is object-oriented and think it is like Java or whatever. In some ways the situation is similar to JavaScript and how so many misunderstand its prototype object system, so they approach it like Java and come away disappointed and very confused. Performance in Ruby can be an issue and much work is being done in that area but it is in the same performance area as Python and other dynamic languages. Programming, thus language design is all about trade-offs, and being able to easily change code at runtime adds power but has performance penalties.

Language wars are pointless. I understand that decisions made can annoy and some might actually like those weird decisions. Python is full of weird decisions, making the language less powerful and more verbose, and for what? I would love to write a “Why I hate Python” article but that serves no useful purpose other than to allow myself to nerd rage for a while. That is not very productive. Python is useful because of third party libraries that link to code written in other languages such as Fortran. These areas include mathematics and “AI” and other libraries like the very well done SQLAlchemy which are more or less the only things compelling about Python which is less about the language and more about the ecosystem. Seriously, there is some fantastic work in done Python.

It is not great that the Ruby ecosystem does not yet have third party libraries in these areas on par with Python’s scientific libraries. Many of the features of Ruby would aid greatly in the ease of programming and flexibility of these types of programs. I have been toying with the idea of building a solid linear algebra library in Ruby that performs well, at least roughly equivalent to Python’s linear algebra libraries. I’ve got nothing but time and I like to keep my math skills up. To my shame, my linear algebra skills were never as good as my understanding of calculus or discrete mathematics. Cold weather is approaching so that might be a fun way to pass a miserable winter and make it less pointless. If I get the basics working well, I don’t have the energy or a consistently clear mind to be running a project, otherwise I wouldn’t be retired. It might be fun and useful to add to my private libraries and maybe just throw it up somewhere and see what happens.

Oddly, talking about programming languages elicits a lot of pointless flame wars and weird arguments that don’t make sense like ‘other languages are flawed so my favorite language is equal’. Okay, that is the default PHP argument since most other languages not named PHP are designed reasonably. Programming is mathematics, mathematics is logic, therefore programming is logic. It is a professional and academic endeavor yet those attributes are often not valued very highly. That is a rant for another day.

This is not a ‘my language is better than yours’ post, especially since I don’t have a language. If I use it, I like it and it is useful for whatever project I am working on. I stopped using languages I don’t like, life is too short. Which is why I can’t tell you much of anything about Python 3 or anything after Java 8. If I need a new language for a new project or to augment an existing one, I will look around for something I can enjoy. Which is what brought me to Kotlin and Elixir. So, I am not a “Ruby programmer”, I am simply a programmer. The best advice to be a developer that anyone can give you is to learn as many languages as you can, in as many categories as you can. At least to the point that you could write something nontrivial in it. Even if you won’t use those languages, you will benefit from it.

Many of the things that Ruby is great at are IO bound anyway and it has a simple foreign function interface(FFI) so writing C libraries or just using existing C libraries is fairly trivial to call from Ruby. The Java Virtual Machine is terrific in compiling long-running code during runtime to produce faster than anything a C compiler can do and a JVM port of Ruby exists called JRuby, which helps a bit although something like Kotlin or Clojure is probably still faster than JRuby. Raw speed is rarely an issue in the majority of problem domains and obsessing about it is a waste of time.

To understand Ruby, it is good to know that it borrowed concepts from many languages and paradigms and brought them in with few, if any changes and made it all work seamlessly. It is remarkable how it can combine various paradigms into coherent syntax and semantics. It was initially developed in Japan by Yukihiro Matsumoto, known as “Matz.” Ruby was publically released in 1995, and the core tenant is focusing on “programmer happiness.” It appeals to the programmer’s sense of aesthetics. It also tries to conform to the Principle of Least Surprise. Of course, while it is well designed, it can surprise, especially before the object system is fully understood. I can say it almost always makes me very happy to use it and most of the time that it surprises me it is a good thing. “Why don’t other languages do it like this!” is something I thought a lot while learning it.

Its string manipulation abilities come from Perl and are about on par with it in its flexibility. I am not sure if it is a good or bad thing, but bizarre things that you can do in Perl, such as create ASCII art of beer bottles that executes and prints the lyrics to ‘99 Bottles of Beer on the Wall’. This can’t be done in Ruby. I least I have never seen it so it cleaned up the considerable mess that is Perl. Ruby also brought over Unix integration from Perl, pretty much as-is. A lot of the built-in functionality in its objects exist to support integration with the underlying operating system. Writing scripts for Unix/Linux is a joy in Ruby. It is not as great on Windows but Windows is not nearly as scriptable as Unix/Linux operating systems, even with Power Shell. Microsoft’s awkwardly named ‘Windows Subsystem for Linux’ makes Ruby more useful in Windows as does the better Windows installers that have been around since Ruby 2.4. It used to be such a pain, especially with installable modules, called Ruby Gems, that is natively compiled. It was not worth the hassles but is pretty simple now, for the most part.

Its object-oriented system comes from Smalltalk which was created by the guy that coined the term.

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

Alan Kay

He has expressed dismay that many allegedly OOP languages bit hard on everything listed except messaging and has said the big idea is message passing. Ruby OOP is indeed all about message passing and not at all like the weird cultish Java-style OO that is full of magical incantations. Ruby objects also hide state - all class and object-level variables are private - but its powerful reflection capabilities make that easy to bypass, so much so that it is possible to trivially test an object’s private methods in total isolation from its public methods if one chooses to do such a thing.

Calling a private method "outside" of the object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Test
  attr_accessor :data #dynamically creates mutator and accessor methods for @data

  def initialize data=[] #arguments can have default values and paranthesis can be used
    @data = data #'@' denotes object level variable, '@@' are class level variables
  end

# everything under the private keyword is private until another modifier is used. There is no need to individually mark each method
private
  def do_something
    puts "array size: #{@data.size}"
  end
end

#run the above code
t=Test.new

t.do_something

t.send :do_something
_a_

t.data = [1,2,3] #data= is the accessor method created by attr_accessor

t.send : do_something

#Output
NoMethodError (private method "do_something" called for #<test:0x0000000006c492e8>)</test:0x0000000006c492e8>
array size: 0
array size: 3

I will discuss some of the above in more detail later.

The method send() is defined in Object and allows messages to be sent to any method in the object, regardless of visibility. So technically the private method is not being called outside of the object. This isn’t all that useful often, but when reflection is useful, it is simple to accomplish in Ruby.

Ruby has powerful list and meta-programming support that is influenced by Lisp. its meta-programming support is extremely well done and allows for a lot of powerful and advanced features. Not as powerful as Lisp but the clean syntax over the headache-inducing abstract-syntax-tree-as-its-syntax style of Lisp is a win in my book. That is not a slam on Lisp, which is many different implementations of the Lambda calculus: the first mathematical construct to prove what can and can not be computed. Lisp isn’t a language but an entire family of them. Lambda calculus was developed by an incredibly smart and influential mathematician, Alonzo Church at Princeton, who was attempting to solve the decision problem, which was a very important open question and one of the big math challenges for that century posed by German mathematician David Hilbert in the 1920s. Several of Church’s doctoral students did foundational work in what would later be called computer science, such as Stephen Kleene. Another more famous student of his is English mathematician Alan Turing who had at the same time had independently solved the decision problem using a made-up construct called Turing Machines. Imagine that, work that was done as a student that normally would have more than justified a doctorate but for the fact that Alonzo Church’s paper that solved the problem was published first. So he moved to the US to study at Princeton under Church and finally earned his doctorate there with Church, as the chair of his doctoral committee, on work not directly related to Turing Machines. It is a fascinating paper and an amazing and sad story. The book is pretty easy to follow, even though the concepts in Turing’s paper are very complex, and it has a lot of interesting mathematical and historical context for Alan Turing’s paper.

Speaking of syntax, its syntax heavily borrows from a rather obscure language called Eiffel among others. The syntax is a little different from other well-known OOP languages and is extremely flexible.

I don’t typically post things in list form but hopefully, it will keep me from rambling too much.

1. Simple, fun, and hides a lot of complexity

The #1 reason I like Ruby is that it is so easy to learn mainly because of a simple and coherent syntax yet supports many advanced features and has done so since the mid-to-late-90’s in many cases.

Like many other programmers, I took to it so easily because it is clean and logical and is by far the quickest that I learned a language to the point I was comfortable and rarely needed to look up docs. It took about a week to get comfortable and after two or three weeks I had a solid grasp on it. I struggled with inspired insanity that is Perl; was annoyed from day one with Python and that annoyance never went away but had no such issues with Ruby. It is a much better Perl. The ability to hide great complexity and make it look simple is its biggest selling point. There is a lot of power and flexibility in that. Indeed, there are often many ways to do something and most of those ways are all equally good. You might notice in my code samples that I often omit parenthesis in method arguments, that makes me happy. Unless there is a need for it because there is an ambiguity that needs to be cleared up, it is pointless noise.

2. Typing system

Ruby is strongly and dynamically typed - this is sometimes called duck typing which is not quite right. A language can be dynamic and the runtime still does type checks. Ruby does not. What it does check is to see if the expected method that is called exists. This is the part that is called duck typing. “If it quacks like a duck and looks like a duck”…

Do not mistake it for weak typing or no typing at all.

Let me repeat it. Ruby is strongly typed.

Strongly Typed
1
2
3
4
5
6
7
8
9
10
11
12
13
  1+'1'
  =>TypeError ("String can't be coerced into Integer")

  #to_i returns the integer representation of the string and does not coerce the string into being an integer
  1+'1'.to_i
  => 2

  # '+' is a method call
  1.+(1)
  =>2

  #Data structures can take any type
  array = [1,true,'string',{key: :value, another_key: 42}]

An object can not ever be another type. Never ever, ever! There is no casting so none of the casting nonsense you will see elsewhere can not be done. That LinkedList you wrote is a LinkedList even if it inherits from List. Even if you are depraved enough to want to do that in Ruby, it won’t let you and there is no need since the language is duck typed. References can change type but not objects and they are not the same thing. References are nothing more than a label, reference is an unfortunate name that many languages use and it confuses people especially when it comes to passing mechanisms.

References are really labels
1
2
3
4
#This is expected in dynamic languages
data = [] #it is an array
data = {} #now it is a hash
data = 1 #it is an integer

Speaking of, passing is pass-by-value only like almost every language. Off the top of my head only C++ out of the mainstream languages - is Pascal a mainstream language anymore? - support pass by reference. Anything in Ruby, Java, etc that looks like pass by reference is simply the consequence of mutable objects. Here is a Java-based article that explains it properly. If you are still confused, think of them as labels.

This typing style allows different objects to be passed to a method without issue as long as the methods on the object that are called exist. This does require a little discipline but it is simple once it is understood. The method ‘map()’ that supports the functional style of programming can take pretty much any data structure that includes the Enumerable module that map() belongs to. Some folks don’t like this flexibility and freedom that it brings. It does help to simplify the code, having a single map() removes the need for multiple implementations, each with different corner cases and bugs. I will talk about it later, but blocks make methods like map infinitely configurable.

I see Java programmers, especially, actually calling some of the built-in reflection methods to check for type. That is unnecessary in Ruby and just complicates code. What matters is does the method called exist on that object, and if not there is a built-in way that can deal with that at runtime. ‘method_missing()’ can be used for various things and is automatically called when an object does not have the method expected. The default implementation is to raise an error that will stop the program but can be overridden. When overriding it you could write a custom error handler and then decide how to proceed. It can also be used to create that wrongly called method dynamically and then call it. That is a rather advanced and obscure feature that would fit in a different article but is pretty neat and is used in some very powerful libraries.

Anyway, don’t do manual type checking, at least not without a great reason even though there are reflection methods to do just that. Oddly, I don’t see programmers, especially coming from Java checking if a method exists on an object which makes more sense than checking for object type but don’t do that either! The methods is_a? and respond_to? have their uses but not to fake something more similar to static type checking, although these checks won’t be run until runtime.

Seriously, don't do this!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test
  def do_something str_val
    puts str_val.reverse if str_val.is_a? String # clean single line if statements

    #don't do this either
    if str_val.respond_to? :upcase
     puts str_val.upcase
    end
  end
end


Test.new.do_something 'string'
#Output
gnirts
STRING

3. Object-Oriented all the way down

Using some reflection methods we can take a peek under the hood.

Everything is an object, even a class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  String.is_a? Object

  #Object hierarchy for String
  String.ancestors

  #Displays all of the methods that String will respond to
  String.methods.sort

  #Output
  true

  [String, Comparable, Object, Kernel, BasicObject]

  [:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :__id__, :__send__, :alias_method, :allocate, :ancestors, :attr, :attr_accessor, :attr_reader, :attr_writer, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :const_source_location, :constants, :define_method, :define_singleton_method, :deprecate_constant, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :itself, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :new, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :remove_method, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :superclass, :taint, :tainted?, :tap, :then, :to_enum, :to_s, :trust, :try_convert, :undef_method, :untaint, :untrust, :untrusted?, :yield_self]

  ---

  class Test
    attr_accessor :data

    def Test.set_var val
      @@class_var = val
    end

    def initialize data=[]
      @data = data
    end
  end

  Test.new.instance_variables

  Test.class_variables

  #Output
  [:@data]

  [:@@class_var]
Even methods are objects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Test
  def do_something
   puts "working hard!"
  end
end

method = Test.new.method :do_something

puts method.is_a? Object
puts method.class

#if do_something had arguments they can be listed
puts method.parameters

#can call into the method since it is a callable
puts method.call


#Output
true

Method

[] #no arguments return an empty array

working hard!
There is no such thing as a static class in Ruby.
1
2
3
4
5
6
7
8
#What looks like a static class is an object that is a child of Class.
class Test
end

Test.instance_of? Class

#Output
true

If writing in an imperative style, Ruby will automatically wrap everything in an object

Even when it doesn't look object-oriented it is
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def function1 arg1
 puts self.class
end

def function2
puts self.methods.size #print the number of methods available in this object
end

#call functions
function1
function2

#output
Object
115
Numeric values are objects
1
2
3
4
5
6
7
8
42.is_a? Object
=>true

42.methods.size
=>145

42.methods.sort
=>[:!, :!=, :!~, :%, :&, :*, :**, :+, :[email protected], :-, :[email protected], :/, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, :>>, :[], :^, :__id__, :__send__, :abs, :abs2, :allbits?, :angle, :anybits?, :arg, :between?, :bit_length, :ceil, :chr, :clamp, :class, :clone, :coerce, :conj, :conjugate, :define_singleton_method, :denominator, :digits, :display, :div, :divmod, :downto, :dup, :enum_for, :eql?, :equal?, :even?, :extend, :fdiv, :finite?, :floor, :freeze, :frozen?, :function1, :function2, :gcd, :gcdlcm, :hash, :i, :imag, :imaginary, :infinite?, :inspect, :instance_eval, :instance_exec, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :integer?, :is_a?, :itself, :kind_of?, :lcm, :magnitude, :method, :methods, :modulo, :negative?, :next, :nil?, :nobits?, :nonzero?, :numerator, :object_id, :odd?, :ord, :phase, :polar, :positive?, :pow, :pred, :private_methods, :protected_methods, :public_method, :public_methods, :public_send, :quo, :rationalize, :real, :real?, :rect, :rectangular, :remainder, :remove_instance_variable, :respond_to?, :round, :send, :singleton_class, :singleton_method, :singleton_method_added, :singleton_methods, :size, :step, :succ, :taint, :tainted?, :tap, :then, :times, :to_c, :to_enum, :to_f, :to_i, :to_int, :to_r, :to_s, :truncate, :trust, :untaint, :untrust, :untrusted?, :upto, :yield_self, :zero?, :|, :~]

As you may have guessed, methods ending with a ‘?’ return a boolean value, true or false. You can make use of this in your classes but returning a boolean is not enforced by the runtime.

The first code example showed how to create accessors and mutators and the parser is flexible enough to make the call look like a simple variable assignment. This includes class level variables. All variables in a class, regardless if they are class or object level are private by default, and can not be made public. Of course, there are reflection methods to change them and even remove them.

Making Ruby write your accessors and mutators
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Test
attr_accessor :data
attr_reader :read_only_var #creates only a 'getter' method

  def initialize
    @data='data'
    @read_only_var="immutible from outside the object?"
    @no_read_or_write="super private"
  end
end

test = Test.new

test.instance_variables
=>[:@data, :@read_only_var, :@no_read_or_write]

test.data = 42
puts test.data
=>42

puts test.read_only_var
=>immutible from outside the object?

#variables are private but there are methods to interact with them

test.instance_variable_set '@read_only_var', :some_symbol
puts test.read_only_var
=>:some_symbol

str = test.instance_variable_get '@no_read_or_write'
puts str
=>super private

test.instance_variable_set '@no_read_or_write', 'changed!'
str = test.instance_variable_get '@no_read_or_write'
puts str
=>changed!

#Variables can be dynamically removed
test.remove_instance_variable '@no_read_or_write'
test.instance_variables
=>[:@data, :@read_only_var]

An OO language where the object does not know about itself and can not manipulate itself is a weak OO language and Ruby has fantastic reflection methods built-in. If the name of a variable or class starts with a capital letter it is constant. String, or any other class or module name is simply a constant. Granted, a lot of them are not commonly useful but when they are, they can save hundreds or more lines compared to other, more stodgy languages.

Judicious use of singleton objects. The references true, false, and nil are built-in singleton objects on a global scope. There is a built-in module to create your singletons, when appropriate. Numeric values singletons also, more or less. Singleton objects are quite useful and are very easy to overuse and abuse but is a useful tool when it really is needed and it is nice to have.

When the Singleton module is included (mixins are covered later) the ‘new()’ method is dynamically made private and the ‘instance()’ method is created.

Singleton objects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
true.class
=>TrueClass

false.class
=>FalseClass

nil.class
=>NilClass

42.object_id == 42.object_id
=>true

require 'singleton'

class SingletonTest
include Singleton
end

#can not instantiate it
st = SingletonTest.new
=>private method new called for SingletonTest:Class (NoMethodError)

st = SingletonTest.instance
st1 = SingletonTest.instance

puts st.object_id==st1.object_id
=>true #should be the same object

#Of course, calling send and then new works since new is only private not removed with the Singleton module
#This should never be done
t = SingletonTest.send :new
puts st.object_id==t.object_id
=>false


#But the built-in singletons have new removed.
NilClass.send :new
=>undefined method new for NilClass:Class (NoMethodError)

# You REALLY don't want a singleton misbehaving
class SingletonTest
  include Singleton
  #Override the constructor to stop others from breaking your singleton
  def self.new
   raise 'Do not create a new object, use instance!'
  end
end

SingletonTest.send :new
=>'Do not create a new object, use instance()!(RuntimeError)

Singleton methods are used to inject new functionality into existing objects. This works on pretty much anything except built-in numeric values.

Singleton methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
value = "42"

puts value.respond_to? :million

def value.million
  (self.to_i * 10**6).to_s
end

puts value.respond_to? :million

puts value.million

#Output
false
true
42000000

#This can be placed in another method to act as a factory to add methods to objects
value = '42'

def million_factory obj
  def obj.million
   (self.to_i * 10**6).to_s
  end
end

million_factory value

puts value.million

#Output
42000000

I will talk about adding functionality to a class so that any object on that class has those newly created methods.

Object constructors and initializers are kept separate, new() and initialize(). When creating an object new() is called: MyObject.new. The method new() is located in BasicObject - the base class for all classes in Ruby - and initialize is created in MyObject - it is optional if you don’t need to initialize data - and is automatically called from new().

The constructor can be overridden but is typically not a great idea - one good reason to do so is in the single class example above - unless your object really needs custom manual memory allocation, which means you will need to write a small C library to allocate and deallocate. If that is the case it might be best to think twice and three times about your class, but I suppose there might be a real need to do so once every 10,000 years. That can be dangerous but for fun and maybe a good prank you can make it completely useless and it won’t create any objects.

If you need to learn about manual memory management - and you do - learn some C. Seriously, learn C if you do not know it. C is an extremely crappy and dangerous language for 99% of use cases and frankly a crappy and dangerous language for the other 1% where it is a good idea to use it, but learning memory management and how things work at a lower level than most languages that you might use is important, even if you never write production code in C.

Since I am meandering off-topic, also learn a bit of a Lisp such as Scheme. Smarter people than me have written extensively on the importance of learning how computers work(C language) and how computation works(Lisp). Ruby can get somewhat close to Lisp but I do not think somewhat close is enough. Learn some assembler also if you can stand it, I learned two assemblers (NASM and SPIM) in college and never enjoyed it, not even a little but it made me understand a bit more about what is going on and made me appreciate languages like C and C++ even though I am more than happy to talk smack about them. As CPU design has marched on, even assembler is getting further and further from CPU instructions but it is still useful to understand things like what instructions a simple loop or other branch looks like and what printing a value to a screen involves in assembler.

Renaming methods is possible via the alias keyword. You could, if perverse enough, rename a method and then override the original method to do whatever or remove it. Of course, who would do such a thing? Oh yeah…

Renaming methods to "Java-fy" it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Test
attr_accessor :data, :another_var
alias set_data data=
alias get_data data
remove_method :data
end

t=Test.new
puts t.respond_to? :set_data
puts t.respond_to? :data=
puts t.respond_to? :get_data
puts t.respond_to? :data

t.set_data 42
puts t.get_data

#Output
true
true
true
false
42

The alias keyword looks a little different from attr_accessor and remove_method. That is because alias is an actual keyword and the other two are methods. There is an alias_method() method but that is probably getting too far into the weeds for this post.

If you don’t need all of the functionality that the built-in subclasses provide there is BasicObject which is the base class and bare bones at that.

BasicObject
1
2
3
4
5
6
7
8
9
10
11
12
13
class Test
end

puts Test.new.methods.size

class Test1 < BasicObject
end

puts Test1.new.methods.size

#Output
58
undefined method `methods' for #<Test1:0x0000000001286b48> (NoMethodError)

The majority of the reflection methods do not exist in BasicObject and none of the system’s functionality exists in BasicObject but as the error message implies, method_missing() is in BasicObject. Ruby calls it a ‘blank class’ but it does have a few methods defined so a class that inherits from it will have some default functionality.

I wish that the methods in Object and Kernel that are more specialized, such as those useful for interacting with the underlying system were put into a separate object hierarchy such as BasicObject -> SystemObject and BasicObject -> ApplicationObject or whatever, it would shrink the number of methods available in all objects whose classes inherit from Object and make things a tad bit cleaner.

You can override a method, but not overload it and you can still call into the parents' version of the method if needed.

Overidding methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Parent
    def do_something arg
        puts 'in parent'
    end
end

class Child < Parent
    def do_something
        puts 'in child'
        super arg1
    end

    def do_something a1,a2=2,a3=4
        puts 'overloading or overriding?'
        super a1
    end
end

c=Child.new

c.do_something
=>do_something: wrong number of arguments (given 0, expected 1..3) (ArgumentError)


c.do_something 1,2,3
=>overloading or overriding?
  in parent

c.do_something 1
=>overloading or overriding?
  in parent

The reason overloading methods are not allowed is that because of these like default argument values in method signatures, a method might take one or more variables and that would cause ambiguity. You can have the same name in the same class if one method is class level and the other is object-level since there is no ambiguity or any inheritance relationship between them. The ‘do_something()’ method with no arguments in Child will never be called, it ends up getting overwritten just like if the second ‘do_something()“ was added dynamically.

You can place multiple class definitions in the same file.

The general object model is single inheritance augmented with mixins, which are covered in a bit.

The object model deserves a series of articles and I hope to produce them soon-ish.

4. Few true operators and keywords

There are few actual operators in Ruby, most are methods. = is a real operator +, -, <, [], {}, etc are methods that you can add to any class. You can not override operators since they are not methods but you can add many types of methods that look like operators and look like part of the overall language. There are examples above that demonstrate it

As already noted, the “keyword” ‘attr_accessor’ is a method, not a keyword, this is powerful as many keyword-like uses can be added to the language in a first-class way, and these “keywords” can be overridden.

Many keywords are methods
1
2
3
4
5
6
class Test
 method(:attr_accessor).class
end

#Output
Method

As will be noted in the next section, everything in Ruby returns a value so pasting this small class into the REPL console, Method will be returned. As will be shown, you can store the value of that class next.

5. Everything is an expression

This is a simple concept that yields powerful results. There are no statements in Ruby. What this means is that everything returns a value, even if it is not helpful and sometimes it is not. What gets returned is the last thing evaluated in the expression and often the keyword ‘return’ in methods is not mandatory since the language automatically returns the last thing evaluated. The return keyword is necessary when returning early and returning multiple individual values which Ruby automatically packs into an array for you.

In this context conditionals, loops, methods, and well everything return a value.

Everything is an expression
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
value = class Test
 method(:attr_accessor).class
end

puts value
=>Method

#this is not useful information
puts "print"
=>nil

#This is completely redundant but conditionals return a value which is the last thing evaluated
a="string"
value  =if a.nil?
 true
else
 'not true'
end
puts value
=> 'not true'


#Methods return the last line evaluated, typically negating the need for 'return'
def do_something
1+4
end

puts do_something
=>5

#But you can add return if you like it redundantly explicit
def do_something
return 1+5
end

#If you need to return early, it is required
def do_something_with_a_number arg
 return -1 if arg==0
 arg * 100
 end

 do_something_with_a_number 10
 => 1000
 do_something_with_a_number 0
 => -1

 #Multiple values can be returned delimited by a comma and ruby will pack it in an array, but return is required
 def multiple_return_values
 return 42, "forty two", :forty_two
 end

 multiple_return_values
 =>[42, "forty two", :forty_two]

 #Or, you can use multiple variables to store them
a,b,c = multiple_return_values
puts a
=> 42
puts b
=>forty two
puts c
=> :forty_two

#If there are not enough variables to store them, the remaining will
#be packed into an array and stored in the last variable
a,b=multiple_return_values
puts a
=> 42
puts b
=>["forty two", :forty_two]

If there are too many variables to store the return values, the extra variables will simply point to nil.

6. Blocks, Procs, and Lambdas

These constructs help to allow changing exactly what a method does at runtime. They are interrelated with Procs and lambdas looking the same on the surface. These will “close over” variables in scope at the point they are called and bring them along to where it is executed. In short, they have the property known as closures. Procs and lambdas look very similar but have important, and confusing differences and it is not always clear why both exist.

Blocks are objects but you can not directly interact with them like a normal object unless you convert them to a Proc. These are passed into methods and there is an implicit reference for them and they are executed with the yield keyword. Blocks are commonly used to change the behavior of a method at runtime such as how to sort an array or use map() on it.

Basic usage of blocks on data structures
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
array = [3,6,2,7,8,5]

#default sort
array.sort
=> [2,3,5,6,7,8]

#force it to sort in reverse
array.sort {|a,b| b<=>a}
=>[8, 7, 6, 5, 3, 2]

#the reference array has not been changed, a copy of the sorted array has been returned in the sort call
puts array
 => [3, 6, 2, 7, 8, 5]

#Force the array to sort itself
array.sort!
=>[2,3,5,6,7,8]

puts array
[2,3,5,6,7,8]

#A simple example of map, multiply each element by 16 for some reason...
array.map {|i| i*16}
=>[32, 48, 80, 96, 112, 128]

The ‘!’ at the end of a method name denotes that it is destructive, that is it mutates the object. For the most part, if it is not appended with the ‘!’, it will return a new object. The exception, and it is annoying is that methods in the standard library that do not have both a destructive and non-destructive method will not have the ‘!’ appended. That bugs me so much. The non-destructive versions are what you want most of the time and definitely if you are writing in a more functional style. Side-effects are bad, mkay?

The final example in this essay makes use of lambda. This is a subject deserving of an essay so is on the todo list.

7. Modules and Mixins

Modules are groupings on related code and yes a module is an object. Methods in modules can be used as “static” methods or designed to be added to other objects dynamically. These are called mixins.

Module methods could be thought of as Java static methods and accessed as such.

Modules are objects but can not be instantiated. Modules can be used to hold classes as a way to namespace them so classes with the same name will not conflict.

Module methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 module Test
   def self.testing
    puts 'testing'
   end

   class NamespacedClass
     def to_s
       "NamespacedClass"
     end
    end
 end

  Test.testing
  => testing

  Test::NamespacedClass.new.to_s
  => NamespacedClass

Since Ruby does not have multiple inheritance, mixins, along with things like singleton methods, add back the advantages of multiple inheritance without the drawbacks. I think the fear of multiple inheritance is massively overstated, much like gotos in C. This is what Java should have had from day 1 instead of interfaces. I guess Java has something similar to mixins now, just very stiff and more verbose than it needs to be which is Java in a nutshell. My decision to never write Java again might be the best decision I ever made, well other than permabanning PHP. I still like the JVM and will write code in other languages to run in the JVM.

I don’t think it is a good idea but you can define object variables in a mixin that can be accessed in typical object methods and vice-versa.

Mixins
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 module Test
   def testing
     @val='mixin variable'
     puts 'mixin testing'
   end
 end

 class MixinTest
   include Test

   def obj_method
     puts @val
   end
 end

 t=MixinTest.new
 t.testing
 t.obj_method

 #Output
 mixin testing
 mixin variable

Both types of methods can exist in a single Module.

8. Open to changes during runtime

Earlier we added a singleton method to an object, here we will add two to a class so that all instances of that class can use them. Let’s add the methods thousand, million, and billion to the built-in Numeric class.

Classes are open to changes at runtime
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Numeric
  def thousand
    self * 10**3
  end

  def million
   self * 10**6 # ie 10 to the 6th power
  end

  def billion
    self * 10**9
  end
end

42.million
=>42000000

42.billion
=>42000000000

#A use case for this might be easier looping here is a method to loop from one value, increment by one each loop until it hits the end
10.thousand.upto(1.million) do |val| # do .. end is the typical way to write multiline blocks but is not enforced, {} works also
 puts val
end
#This will print every value from 10,000 to 1 million
#Most looping methods are built into the object or part of modules like Enumerable

Load the file this is in as part of the program startup and it will be available everywhere in the program. This is the most straightforward way to do it, opening the eigenclass is another and will be talked about in the series on the Ruby object model whenever I get to it.

This is extremely useful in all sorts of contexts. Some libraries use this for date and time management, such as ‘5.days.ago’ and things like that. It keeps a clean interface at the cost of cluttering up the implementation a little bit.

For part of completing my master’s degree, I wrote a program that took a lot of popular security and network tools and would use the output of one as input for others and merged it into a semi-coherent platform to automate security testing and print out a detailed report with little human interaction.

To facilitate creating reports I developed a library to handle all of that messiness. It can print out to screen, text, and PDF, and also HTML. It runs as a singleton object, initialize() loads the expected format module but only one, and if needed, when a different format is requested the object would mutate and print in the new format.

Yes, I could have had multiple classes such as TextPrinter, PDFPrinter, etc but where is the fun in that? A single ReportPrinter class was all that was needed. Any specialized code was pushed out in modules meant to be mixed in. If I had multiple print classes all of that would be subclassed or the code repeated again and again. In hindsight, it would have been better if ReportPrinter was the mixin to the format classes but the way I did it worked out well.

At first glance, it looks like it might be weird for the sake of being weird but there were three reasons I did it this way. It was fun, it turned out to be very flexible and my thesis adviser wanted me to add in some creative examples of Ruby since she was unfamiliar with the language. It turned out well, at some point in my project I was told it needed a way to print it out the report in HTML for some unfathomable reason - at least she didn’t ask for a Word format print feature - and it required a single module for the HTML formatting and nothing else needed to be changed. There was already a format query tool that just picked it up and offered it as an option in the user interface with nothing extra to do on my part. It is just seamless on the part of the programmer and user of my hacking tool.

It is very useful in many contexts, I have thought about releasing it as open-source, maybe I will. It was the last part of my master’s project so it is a bit messy and I had to start writing a paper describing the project which took a while since it ended up at about 110 pages. I only had to do a project or a thesis but it somehow turned into both.

The project almost got out of control because as usual, I overdo things - as this article has become - and get way over-optimistic. If I had not listened to one of my professors who read my proposal, I would still be working on it, nearly 12 years after starting it, because the scope of the project was initially insane. Even so, I let a few features removed for sanity to creep back in so the project and thesis took about 2 years before it was ready for the presentation and defense.

If you are curious, the user interface was in standard Java using Swing, which I wrote using JRuby. The actual working code is just plain Ruby files. The Swing library was even less annoying than normal to use since I was able to leverage blocks in passing functionality to the Swing widgets. At the time I was writing my thesis, Java had no sane way to use closures and this allowed me to avoid the insanity of anonymous inner classes and keep the line of code count way down in the Swing code. I also used the excellent mig Layout library, further reducing the complexity of the Java code. Just thinking about Java makes me want to vent my seething hatred for that overly verbose mess. Love the Java runtime environment though, I hope Ruby’s runtime can get as advanced and awesome at some point or at least somewhat close to it. It is why I am interested in the Kotlin language but that is for another article.

9. Symbols

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 in the program that are the same, really 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 key values in hashes and to set options in objects or entire programs in the initialization.

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 ’ and can also download specific versions. It can also 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 for the programmer is a simple file called Gemfile. Gems can be grouped as needed like ‘development’, ‘production’, or ‘testing’ and can even keep things separate between those. 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. It will manage dependencies of gems you do include without any extra work. It is a Ruby file with a few methods and blocks passed to them, 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 and only HTML files are uploaded to my server.

Gemfile Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
source "https://rubygems.org"

group :development do
  gem 'rake', '~> 10.0'
  gem 'jekyll', '~> 2.0'
  gem 'octopress-hooks', '~> 2.2'
  gem 'octopress-date-format', '~> 2.0'
  gem 'jekyll-sitemap'
  gem 'rdiscount', '~> 2.0'
  gem 'RedCloth', '~> 4.2.9'
  gem 'haml', '~> 4.0'
  gem 'compass', '~> 1.0.1'
  gem 'sass-globbing', '~> 1.0.0'
  gem 'rb-fsevent', '~> 0.9'
  gem 'stringex', '~> 1.4.0'
  gem 'rack-rewrite'
end

gem 'sinatra', '~> 1.4.2'

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 but like mentioned there is work being done, some of the work is out with the last version, 2.7.0, and more work is 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 to be very fast, yet it is possible to write extremely poor 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 do exist C interpreters that obviously will not perform as well. Certainly, 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 network or a database, spending a lot of time optimizing your code or using a faster language but might take significantly more work to write isn’t as useful 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 of that, it is lacking many of the meta-programming and reflection features of Ruby. Changing and adding code at runtime is rather 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 looks to be far less complex syntax-wise that I have seen so far. 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 nice to try out. 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 but 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 either. 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 to me. One such example is Refinements, which was introduced way back with Ruby 2.0 if I am remembering correctly. This 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 but 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. 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 that only fairly recently have been worked on, 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. Even many design patterns simply vanish, perhaps redundant is a better descriptor, in Ruby because of the way it is designed. You get a lot of that for free.

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; but the interfaces are simple, clean, powerful and can easily bend however the programmer wishes.

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.

This is a simple example of an accumulator. It starts at a value and simply 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. The example starts with a function that creates the accumulator lambda and then just passes in values. To get the current value, pass in 0 which gets annoying and since lambda is an object, let’s 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
1
2
3
4
5
6
7
8
9
10
11
  def accumulator val=0
    lambda {|add| val += add } #add is the argument passed into the function the lambda closes over val
  end

  acc = accumulator
  puts acc.call 5
  puts acc[10] #[] is actually used here as an alias of call

  #Output
  5
  15

Using call or [] is kind of clunky so let’s make it work a little better.

Add more useful functionality to the lambda
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def accumulator val=0
  lambda {|add| val += add } #add is the argument passed into the function the lambda closes over val
end

acc = accumulator

def acc.+ val
 self.call val
end

def acc.value
  self.call 0
end

#reset the accumulator value to 0
def acc.reset
   self.call self.value*-1
end

acc + 20
puts acc.value
acc +  -15
puts acc.value
acc.reset
puts acc.value

#Output
20
5
0

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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def create_accumulator val=0
  l=lambda {|add| val+=add}
  #open the lambda object's eigenclass to make it simpler to add methods
  #explicit self's are not necessary since we are inside the lambda object
  class << l
    def + val
      call val
    end

    def value
      call 0
    end

    #reset the accumulator value to 0
    def reset
    #both call and value are methods, paranthesis might make that a little clearer
    #of course * is also a method: call(value().*(-1)) or more simply call(value() * -1)
      call value * -1
    end
  end
  l #return the lambda
end

acc = create_accumulator

acc + 7
puts acc.value
acc + 22
puts acc.value
acc.reset
puts acc.value

#Output
7
29
0
A more straightforward and sane OOP approach
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Accumulator
  #the mutator value=() doesn't seem to make a lot of semantic sense
  #so we only create a getter method
  attr_reader :value

  def initialize val=0
   @value=val
  end

  def + val
   @value+=val
  end

  def reset
    @value = 0
  end
end

acc = Accumulator.new
acc + 100
puts acc.value
acc+ -300
puts acc.value
acc.reset

#Output
100
-200
0

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 to much better use though. 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.

Comments