2. Typing system
Ruby is strongly and dynamically typed - this is sometimes called duck typing, which is not quite right, but I suppose it is close enough.
A language can be dynamic and the runtime still does type checks. Ruby does not. What it does is check to see if the method that is called for a given object exists or not.
This is the part that is called duck typing. “If it walks like a duck and quacks like a duck and looks like a duck”…
Do not mistake it for weak typing or no typing at all.
Let me repeat that. Ruby is strongly typed.
The third line shows that ‘+’ is a method on the object 1, not an operator. Operator overloading is not allowed in Ruby.
The last line demonstrates that data structures can take any type. They will sort deterministically even if every type in the collection is different.
The output for the three lines:
An object can not ever be another type. Never ever, ever!
There is no casting. None of the casting nonsense that you see elsewhere can be done in Ruby. That LinkedList you wrote is a LinkedList even if it inherits from List. It will never just be a 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.
Ruby References are really labels
Speaking of, passing in Ruby is pass-by-value only. That is like almost every language in existence. 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 so 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 the flexibility and freedom that it brings. It does help to simplify the code. Not having to write lots of map methods to account for all sorts of types, concrete and generic. Each with different corner cases and bugs simplifies things greatly. I will talk about it later, but blocks make methods like map() infinitely configurable.
I see Java programmers, calling some of the built-in reflection methods in Riby to check for type.
That is unnecessary in Ruby, and it just complicates the code. What matters is if the method called exists on that object. 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 it 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. That makes more sense than checking for the 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!
Running the class:
3. Object-Oriented all the way down
Using some reflection methods, we can use to take a peek under the hood.
Everything is an object, even a class
Even methods are objects
There is no such thing as a static class in Ruby.
If writing in an imperative style, Ruby will automatically wrap everything in an object
Even when it doesn’t look object-oriented it is
Numeric values are objects
As you may have guessed, methods ending with a ‘?’ return a boolean value, true or false. You can use 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. 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
Run the Test class
An OO language that has objects that do not know about themselves and can not manipulate themselves easily is poorly designed.
Ruby has fantastic reflection methods built-in. 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.
If the name of a variable or anything in the language starts with a capital letter it is a constant. String, or any other class or module name is a constant.
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 singletons, when appropriate. Numeric values are singleton objects also. Singleton objects are quite useful. They are very easy to overuse and abuse. They are a useful tool when it 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 methods are used to inject new functionality into existing objects. This works on pretty much anything except built-in numeric values.
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. BasicObject is the base class for all classes in Ruby. 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 singleton class example above - unless your object really needs custom manual memory allocation, like really really needs that. That means you will need to write a small C library to allocate and deallocate memory. If that is the case, it might be best to think twice and three times about your class. I suppose there might be a real need to do so once every 10,000 years. That can be dangerous.
For fun and maybe a good prank, you can make it completely useless so it cannot 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.
Frankly, it is a crappy and dangerous language for the other 1% where it is a good idea to use it. 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, learning a bit of Lisp, such as Scheme is a good idea. Smarter people than me have written extensively on the importance of learning how computers work(C language or lower level) and how computation works(Lisp).
Ruby can get somewhat close to Lisp, but I do not think that 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. It 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 what instructions a simple loop or other branch get produced and what printing a value to a screen involves in assembler. Without knowing a bit of assembler, debugging compiled code is a nightmare.
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.
The alias keyword looks a little different from attr_accessor and remove_method.
That is because alias is a 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 for all Ruby objects, and bare-bones at that.
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’. 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. Something like 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 - this may change in Ruby 3. You can still call into the parents’ version of the method if needed.
Ruby Overidding methods
The reason overloading methods are not allowed is that because of default argument values in method signatures, 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.
Continued here sigh