Why I like Ruby 2
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.
1
2
3
4
5
6
7
1 +'1'
1 + '1'.to_i
1.+(1)
array = [1,2.0,'string']
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:
1
2
3
4
5
6
7
TypeError ('String cannot be coerced into Integer')
2
2
[1,2.0,'string']
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
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 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 built-in reflection methods in Ruby 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!
1
2
3
4
5
6
7
8
class Test
def do_something str_val
puts str_val.reverse if str_val.is_a? String # clean single line if statements
if str_val.respond_to? :upcase
puts str_val.upcase
end
end
end
Running the object:
1
2
3
4
5
6
7
Test.new.do_something 'string'
#Output
gnirts
STRING
Test.new.do_something :not_a_string
# It won't print anything
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
1
2
3
4
5
6
7
String.is_a? Object
#Object hierarchy for String
String.ancestors
#Displays all of the methods that String will respond to
String.methods.sort
Output
1
2
3
4
5
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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
1
2
3
4
[:@data]
[:@@class_var]
Even methods are objects
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
1
2
3
4
5
6
7
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
=>[:!, :!=, :!~, :%, :&, :*, :**, :+, :+@, :-, :-@, :/, :<, :<<, :<=, :<=>, :==, :===, :=~, :>, :>=, :>>, :[], :^, :__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 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
1
2
3
4
5
6
7
8
9
10
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
Run the Test 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
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
=>immutable 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 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 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
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)
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
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 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.
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 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.
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’. 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
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 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
This post is licensed under
CC BY 4.0
by the author.
Trending Tags
Comments powered by Disqus.