Demonstrate Ruby's six characteristics that are easy to be misunderstood with actual code. The Code demonstrates ruby misunderstanding.
Introduction: if you are a C ++ developer, you need to use Ruby to quickly execute some prototype designs. When you pick up a Ruby reference book (such as Pickaxe) or browse a Ruby website, you will see some familiar structures, such as class declarations, thread support, and exception handling. When you think you understand how Ruby works, you realize that the concurrency mechanism in your Ruby code is different from that in the Boost thread, catch and throw are also different from what they look like, and others use keywords named self in their Ruby scripts. Welcome to the Ruby world!
If you are a C ++ programmer and need to work in the Ruby environment, you have some homework to do. This article discusses six Ruby features that Ruby beginners may misunderstand, especially when he or she comes from a similar but different environment, such as C ++:
● Ruby class hierarchy
● Singleton method in Ruby
● Self keywords
● Method_missing Method
● Exception Handling
● Thread
Note: All the code in this article is tested based on Ruby version 1.8.7.
Class hierarchy in Ruby
The class hierarchy in Ruby is tricky. Create a Cat class and explore its hierarchy (see Listing 1 ).
Listing 1. Implicit class hierarchy in Ruby
irb (main): 092: 0> class Cat
irb (main): 093: 1> end
= nil
irb (main): 087: 0> c = Cat.new
=> # <Cat: 0x2bacb68>
irb (main): 088: 0> c.class
Cat
irb (main): 089: 0> c.class.superclass
=> Object
irb (main): 090: 0> c.class.superclass.superclass
= nil
irb (main): 091: 0> c.class.superclass.superclass.superclass
NoMethodError: undefined method `superclass' for nil: NilClass
from (irb): 91
from: 0
All objects in Ruby (even user-defined objects) are descendants of the Object class, which is clearly visible in Listing 1. This is in stark contrast to C ++. This is nothing like ordinary data types, such as C / C ++ int or double. Listing 2 shows the class hierarchy for the integer 1.
Listing 2. Class hierarchy for integer 1
irb (main): 100: 0> 1.class
=> Fixnum
irb (main): 101: 0> 1.class.superclass
=> Integer
irb (main): 102: 0> 1.class.superclass.superclass
= Numeric
irb (main): 103: 0> 1.class.superclass.superclass.superclass
=> Object
So far everything went smoothly. You now know that the class itself is an object of type Class. And Class is ultimately derived from Object, as shown in Listing 3 using Ruby's built-in String class.
Listing 3. Class hierarchy of classes
irb (main): 100: 0> String.class
=> Class
irb (main): 101: 0> String.class.superclass
=> Module
irb (main): 102: 0> String.class.superclass.superclass
=> Object
Module is the base class of Class, but one thing to keep in mind when using it is that you cannot directly instantiate a user-defined Module object. If you don't want to go deep into Ruby, it's best to consider Modules that have similar characteristics to the C ++ namespace: you can define your own methods, constants, and so on. You included a Module in Class and voilà, and all elements of Module will now magically become elements of Class. Listing 4 provides an example.
Listing 4. Module cannot be instantiated directly and can only be used with classes
irb (main): 020: 0> module MyModule
irb (main): 021: 1> def hello
irb (main): 022: 2> puts "Hello World"
irb (main): 023: 2> end
irb (main): 024: 1> end
irb (main): 025: 0> test = MyModule.new
NoMethodError: undefined method `new 'for MyModule: Module
from (irb): 25
irb (main): 026: 0> class MyClass
irb (main): 027: 1> include MyModule
irb (main): 028: 1> end
=> MyClass
irb (main): 029: 0> test = MyClass.new
=> # <MyClass: 0x2c18bc8>
irb (main): 030: 0> test.hello
Hello World
= nil
Let's reiterate the point: when you write c = Cat.new in Ruby, c is an object of type Cat derived from Object. The Cat class is an object of type Class. Class is derived from Module, and Module is derived from Object. This object and its type are therefore valid Ruby objects.
Singleton methods and editable classes
Now, look at the singleton method. Suppose you want to use C ++ to model something similar to human society. So how would you do it? Define a class named Human and then millions of Human objects? It's more like modeling a rigid society; everyone must have unique characteristics. Ruby's singleton method comes in handy here, as shown in Listing 5.
Listing 5.Singleton method in Ruby
irb (main): 113: 0> y = Human.new
=> # <Human: 0x319b6f0>
irb (main): 114: 0> def y.paint
irb (main): 115: 1> puts "Can paint"
irb (main): 116: 1> end
= nil
irb (main): 117: 0> y.paint
Can paint
= nil
irb (main): 118: 0> z = Human.new
=> # <Human: 0x3153fc0>
irb (main): 119: 0> z.paint
NoMethodError: undefined method `paint 'for # <Human: 0x3153fc0>
from (irb): 119
Singleton methods in Ruby are methods that are only associated with specific objects and cannot be used for general classes. They are prefixed with the object name. In Listing 5, the paint method is specific to the y object and is limited to the y object; z.paint causes a "method undefined" error. You can call singleton_methods to find out the list of singleton methods in an object:
irb (main): 120: 0> y.singleton_methods
=> ["paint"]
But there is another way to define singleton methods in Ruby. Take a look at the code in Listing 6.
Listing 6. Another way to create a singleton method
irb (main): 113: 0> y = Human.new
=> # <Human: 0x319b6f0>
irb (main): 114: 0> class << y
irb (main): 115: 1> def sing
irb (main): 116: 1> puts "Can sing"
irb (main): 117: 1> end
irb (main): 118: 1> end
= nil
irb (main): 117: 0> y.sing
Can sing
= nil
Listing 5 also opens up new possibilities to add new methods to user-defined classes and built-in Ruby existing classes, such as String. This is not possible in C ++ unless you have access to the source code of the class you are using. Observe the String class again (Listing 7).
Listing 7. Ruby allows you to modify an existing class
irb (main): 035: 0> y = String.new ("racecar")
=> "racecar"
irb (main): 036: 0> y.methods.grep (/ palindrome /)
=> []
irb (main): 037: 0> class String
irb (main): 038: 1> def palindrome?
irb (main): 039: 2> self == self.reverse
irb (main): 040: 2> end
irb (main): 041: 1> end
irb (main): 050: 0> y.palindrome?
=> true
Listing 7 clearly shows how to edit an existing Ruby class to add your own method of choice. Here, I added the palindrome? Method to the String class. So Ruby classes are editable at runtime (a powerful property).
Now that you know a bit about Ruby's class hierarchy and singletons, let's look at self. Note that I used self when defining the palindrome? Method.
Find self
Probably the most common use of the self keyword is to declare a static method in a Ruby class, as shown in Listing 8.
Listing 8.Declaring a static method of a class with self
class SelfTest
def self.test
puts "Hello World with self!"
end
end
class SelfTest2
def test
puts "This is not a class static method"
end
end
SelfTest.test
SelfTest2.test
As you can see from the output of Listing 8 (as shown in Listing 9), you cannot call non-static methods without objects. The behavior is similar to C ++.
Listing 9. Error when calling a non-static method without an object
irb (main): 087: 0> SelfTest.test
Hello World with self!
= nil
irb (main): 088: 0> SelfTest2.test
NoMethodError: undefined method 'test' for SelfTest2: Class
from (irb): 88
Before exploring the more esoteric uses and meanings of self, note that you can also define a static method in Ruby by prefixing the method name with the class name:
class TestMe
def TestMe.test
puts "Yet another static member function"
end
end
TestMe.test # works fine
Listing 10 provides a more interesting but not easy to find use of self.
Listing 10. Using metaclasses to declare static methods
class MyTest
class << self
def test
puts "This is a class static method"
end
end
end
MyTest.test # works fine
This piece of code defines test as a class static method in a slightly different way. To understand what exactly happened, you need to look at some details of the class << self syntax. class << self… end creates a metaclass. In the method lookup chain, the object's metaclass is searched before the base class of the object is accessed. If you define a method in a metaclass, you can call that method on the class. This is similar to the concept of static methods in C ++.
Can I access a metaclass? Yes: Just return self from class << self… end. Note that in a Ruby class declaration, you are not obliged to give only method definitions. Listing 11 shows the metaclass.
Listing 11. Understanding metaclasses
irb (main): 198: 0> class MyTest
irb (main): 199: 1> end
= nil
irb (main): 200: 0> y = MyTest.new
=> # <MyTest: 0x2d43fe0>
irb (main): 201: 0> z = class MyTest
irb (main): 202: 1> class << self
irb (main): 203: 2> self
irb (main): 204: 2> end
irb (main): 205: 1> end
=> # <Class: MyTest>
irb (main): 206: 0> z.class
=> Class
irb (main): 207: 0> y.class
=> MyTest
Going back to the code in Listing 7, you see that palindrome is defined as self == self.reverse. In this context, self is no different from C ++. Both C ++ and Ruby methods are required An operation object to modify or extract status information. self refers to this object here. Note that you can optionally call public methods by appending the self prefix to indicate the object to which the method will act, as shown in Listing 12.
Listing 12. Invoking method with self
irb (main): 094: 0> class SelfTest3
irb (main): 095: 1> def foo
irb (main): 096: 2> self.bar ()
irb (main): 097: 2> end
irb (main): 098: 1> def bar
irb (main): 099: 2> puts "Testing Self"
irb (main): 100: 2> end
irb (main): 101: 1> end
= nil
irb (main): 102: 0> test = SelfTest3.new
=> # <SelfTest3: 0x2d15750>
irb (main): 103: 0> test.foo
Testing Self
= nil
In Ruby you cannot call private methods by appending the self keyword prefix. For a C ++ developer, this can be a bit confusing. The code in Listing 13 makes clear that self cannot be used on private methods: calls to private methods can only be made on implicit objects.
Listing 13. self cannot be used for private method calls
irb (main): 110: 0> class SelfTest4
irb (main): 111: 1> def method1
irb (main): 112: 2> self.method2
irb (main): 113: 2> end
irb (main): 114: 1> def method3
irb (main): 115: 2> method2
irb (main): 116: 2> end
irb (main): 117: 1> private
irb (main): 118: 1> def method2
irb (main): 119: 2> puts "Inside private method"
irb (main): 120: 2> end
irb (main): 121: 1> end
= nil
irb (main): 122: 0> y = SelfTest4.new
=> # <SelfTest4: 0x2c13d80>
irb (main): 123: 0> y.method1
NoMethodError: private method `method2 'called for # <SelfTest4: 0x2c13d80>
from (irb): 112: in `method1 '
irb (main): 124: 0> y.method3
Inside private method
= nil
Since everything in Ruby is an object, when you call self at the irb prompt, you get the following result:
irb (main): 104: 0> self
=> main
irb (main): 105: 0> self.class
=> Object
As soon as you start irb, the Ruby interpreter creates the master object for you. This main object is also called the top-level context in Ruby-related articles.
So much for self. Let's move on to dynamic methods and method_missing methods.
method_missing
Take a look at the Ruby code in Listing 14.
Listing 14. method_missing in action
irb (main): 135: 0> class Test
irb (main): 136: 1> def method_missing (method, * args)
irb (main): 137: 2> puts "Method: # {method} Args: (# {args.join (',')})"
irb (main): 138: 2> end
irb (main): 139: 1> end
= nil
irb (main): 140: 0> t = Test.new
=> # <Test: 0x2c7b850>
irb (main): 141: 0> t.f (23)
Method: f Args: (23)
= nil
Obviously, if voodoo is your favorite, then Listing 14 will give you this grace. What happened here? We created an object of type Test and then called t.f with 23 as a parameter. But Test does not use f as a method, and you should get a NoMethodError or similar error message. Ruby does a great thing here: your method calls are blocked and processed by method_missing. The first parameter of method_missing is the missing method name, in this case f. The second (and last) parameter is * args, which captures the parameters passed to f. Where can you use parameters like this? Among the many options, you can easily forward a method call to an included Module or a component object without explicitly providing a wrapper application programming interface for each call in the top-level class.
See more voodoo in Listing 15.
Listing 15. Passing parameters to a routine using the send method
irb (main): 142: 0> class Test
irb (main): 143: 1> def method1 (s, y)
irb (main): 144: 2> puts "S: # {s} Y: # {y}"
irb (main): 145: 2> end
irb (main): 146: 1> end
= nil
irb (main): 147: 0> t = Test.new
irb (main): 148: 0> t.send (: method1, 23, 12)
S: 23 Y: 12
= nil
In Listing 15, class Test has a method named method1 defined. However, instead of calling the method directly, a call to the send method is issued. send is a public method of the Object class, so it can be used for Test (remember, all classes are derived from Object). The first parameter of the send method is a symbol and string representing the method name. What can the send method do that you usually can't? You can use the send method to access the private methods of a class. Of course, it is still controversial whether this is a good feature. Take a look at the code in Listing 16.
Listing 16. Accessing class private methods
irb (main): 258: 0> class SendTest
irb (main): 259: 1> private
irb (main): 260: 1> def hello
irb (main): 261: 2> puts "Saying Hello privately"
irb (main): 262: 2> end
irb (main): 263: 1> end
= nil
irb (main): 264: 0> y = SendTest.new
=> # <SendTest: 0x2cc52c0>
irb (main): 265: 0> y.hello
NoMethodError: private method `hello 'called for # <SendTest: 0x2cc52c0>
from (irb): 265
irb (main): 266: 0> y.send (: hello)
Saying Hello privately
= nil
Throw and catch are not superficial
If you have a background in C ++ like me and are trying to write exception-safe code, you will start to feel unusually kind when you see that Ruby has throw and catch keywords. Unfortunately, throw and catch have completely different meanings in Ruby.
Ruby typically uses begin ... rescue blocks to handle exceptions. Listing 17 provides an example.
Listing 17.Exception handling in Ruby
begin
f = File.open ("ruby.txt")
# .. continue file processing
rescue ex => Exception
# .. handle errors, if any
ensure
f.close unless f.nil?
# always execute the code in ensure block
end
In Listing 17, if something goes wrong when trying to open the file (possibly because of a missing file or file permissions), the code in the rescue block runs. The code in the ensure block always runs, regardless of whether any exceptions are raised. Note that whether the rescue block is immediately followed by the ensure block is optional. Also, if an exception must be thrown explicitly, the syntax is raise <MyException>. If you choose to have your own exception class, you may want to derive the same class from Ruby's built-in Exception class to take advantage of existing methods.
The catch and throw code blocks in Ruby are not actually exception handling: you can use throw to modify program flow. Listing 18 shows an example using throw and catch.
Listing 18.Throw and catch in Ruby
irb (main): 185: 0> catch: label do
irb (main): 186: 1 * puts "This will print"
irb (main): 187: 1> throw: label
irb (main): 188: 1> puts "This will not print"
irb (main): 189: 1> end
This will print
= nil
In Listing 18, when the code reaches the throw statement, execution is interrupted and the interpreter starts looking for a catch block that processes the corresponding symbol. Continue execution at the end of the catch block. Check out the throw and catch examples in Listing 19: Note that you can easily use catch and throw statements for individual functions.
Some people even say that the support for catch and throw in Ruby takes C goto behavior to a whole new level. Given that functions can have multiple levels of nesting, and catch blocks may be at each level, the goto behavior analogy seems to be well-documented.
Listing 19. Exception handling in Ruby: nested catch blocks
irb (main): 190: 0> catch: label do
irb (main): 191: 1 * catch: label1 do
irb (main): 192: 2 * puts "This will print"
irb (main): 193: 2> throw: label
irb (main): 194: 2> puts "This won't print"
irb (main): 195: 2> end
irb (main): 196: 1> puts "Neither will this print"
irb (main): 197: 1> end
This will print
= nil
Threads in Ruby can be green
Ruby version 1.8.7 does not support true concurrency. Not really supported. But you would say that there is a Thread constructor in Ruby. You are right. But this Thread.new does not spawn a real operating system thread every time you call the same method. Ruby supports green threads: The Ruby interpreter uses a single operating system thread to handle workloads from multiple application-level threads.
This "green thread" concept is useful when a thread is waiting for some input / output to occur, and you can easily schedule a different Ruby thread to take full advantage of the CPU. But this constructor does not work with modern multi-core CPUs (Wikipedia provides a section that explains what a green thread is. See Resources for a link).
This last example (see Listing 20) proves this.
Listing 20. Multiple threads in Ruby
#! / usr / bin / env ruby
def func (id, count)
i = 0;
while (i <count)
puts "Thread # {i} Time: # {Time .now} "
sleep (1)
i = i + 1
end
end
puts "Started at # {Time.now}"
thread1 = Thread.new {func (1, 100)}
thread2 = Thread.new {func (2, 100)}
thread3 = Thread.new {func (3, 100)}
thread4 = Thread.new {func (4, 100)}
thread1.join
thread2.join
thread3.join
thread4.join
puts "Ending at # {Time.now}"
Suppose you have a top utility on your Linux? Or UNIX? Machine, run the code in a terminal, get the process ID, and then run top -p <process id>. After top starts, hold down Shift-H to list the number of running threads. You should only see one thread, confirming this: Concurrency in Ruby 1.8.7 is just a myth.
Overall, there is no harm in green threads. They are still useful in heavy-duty input / output-intensive programs, not to mention that this method is probably the most portable among operating systems.
Concluding remarks
This article covers the following areas:
● The concept of class hierarchy in Ruby
● Singleton method
● Explain the self keyword and method_missing method
● abnormal
● Thread
Although Ruby is unique, programming with it is fun, and its ability to do a lot of work with minimal code is very powerful. No wonder large applications like Twitter use Ruby to harness its true potential. I wish you a happy Ruby programming experience!