Section 1.5. Training Your Intuition: Things to Remember


1.4. Dynamic Aspects of Ruby

Ruby is a dynamic language in the sense that objects and classes may be altered at runtime. Ruby has the capability to construct and evaluate pieces of code in the course of executing the existing statically coded program. It has a sophisticated reflection API that makes it more "self-aware"; this enables the easy creation of debuggers, profilers, and similar tools and also makes certain advanced coding techniques possible.

This is perhaps the most difficult area a programmer will encounter in learning Ruby. In this section we briefly examine some of the implications of Ruby's dynamic nature.

1.4.1. Coding at Runtime

We have already mentioned load and require. But it is important to realize that these are not built-in statements or control structures or anything of that nature; they are actual methods. Therefore it is possible to call them with variables or expressions as parameters or to call them conditionally. Contrast with this the #include directive in C or C++, which is evaluated and acted on at compile time.

Code can be constructed piecemeal and evaluated. As another contrived example, consider this calculate method and the code calling it:

def calculate(op1, operator, op2)   string = op1.to_s + operator + op2.to_s     # operator is assumed to be a string; make one big     # string of it and the two operands   eval(string)   # Evaluate and return a value end @alpha = 25 @beta = 12 puts calculate(2, "+", 2)        # Prints 4 puts calculate(5, "*", "@alpha") # Prints 125 puts calculate("@beta", "**", 3) # Prints 1728


As an even more extreme example, the following code prompts the user for a method name and a single line of code; then it actually defines the method and calls it:

puts "Method name: " meth_name = gets puts "Line of code: " code = gets string = %[def #{meth_name}\n #{code}\n end]   # Build a string eval(string)                                   # Define the method eval(meth_name)                                # Call the method


Frequently programmers want to code for different platforms or circumstances and still maintain only a single code base. In such a case, a C programmer would use #ifdef directives, but in Ruby, definitions are executed. There is no "compile time," and everything is dynamic rather than static. So if we want to make some kind of decision like this, we can simply evaluate a flag at runtime:

if platform == Windows   action1 elsif platform == Linux   action2 else   default_action end


Of course, there is a small runtime penalty for coding in this way since the flag may be tested many times in the course of execution. But this example does essentially the same thing, enclosing the platform-dependent code in a method whose name is the same across all platforms:

if platform == Windows   def my_action     action1   end elsif platform == Linux   def my_action     action2   end else   def my_action     default_action   end end


In this way, the same result is achieved, but the flag is only evaluated once; when the user's code calls my_action, it will already have been defined appropriately.

1.4.2. Reflection

Languages such as Smalltalk, LISP, and Java implement (to varying degrees) the notion of a reflective programming languageone in which the active environment can query the objects that define itself and extend or modify them at runtime.

Ruby allows reflection quite extensively but does not go as far as Smalltalk, which even represents control structures as objects. Ruby control structures and blocks are not objects (A Proc object can be used to "objectify" a block, but control structures are never objects.)

The keyword defined? (with an appended question mark) may be used to determine whether an identifier name is in use:

if defined? some_var   puts "some_var = #{some_var}" else   puts "The variable some_var is not known." end


Similarly, the method respond_to? determines whether an object can respond to the specified method call (that is, whether that method is defined for that object). The respond_to? method is defined in class Object.

Ruby supports runtime type information in a radical way. The type (or class) of an object can be determined at runtime using the method type (defined in Object). Similarly, is_a? tells whether an object is of a certain class (including the superclasses); kind_of? is an alias. For example:

puts "abc".class ""# Prints String puts 345.class # Prints Fixnum rover = Dog.new print rover.class # Prints Dog if rover.is_a? Dog   puts "Of course he is." end if rover.kind_of? Dog   puts "Yes, still a dog." end if rover.is_a? Animal   puts "Yes, he's an animal, too." end


It is possible to retrieve an exhaustive list of all the methods that can be invoked for a given object; this is done by using the methods method, defined in Object. There are also variations such as private_instance_methods, public_instance_methods, and so on.

Similarly, you can determine the class variables and instance variables associated with an object. By the nature of OOP, the lists of methods and variables include the entities defined not only in the object's class but also in its superclasses. The Module class has a method constants used to list the constants defined within a module.

The class Module has a method ancestors that returns a list of modules included in the given module. This list is self-inclusive; Mod.ancestors will always have at least Mod in the list. This list comprises not only parent classes (through inheritance) but "parent" modules (through module inclusion).

The class Object has a method superclass that returns the superclass of the object or returns nil. Because Object itself is the only object without a superclass, it is the only case in which nil will be returned.

The ObjectSpace module is used to access any and all "living" objects. The method _idtoref can be used to convert an object ID to an object reference; it can be considered the inverse of the colon notation. ObjectSpace also has an iterator called each_object that iterates over all the objects currently in existence, including many that you will not otherwise explicitly know about. (Remember that certain small immutable objects, such as objects of class Fixnum, NilClass, trueClass, and FalseClass are not kept on the stack for optimization reasons.)

1.4.3. Missing Methods

When a method is invoked (myobject.mymethod), Ruby first searches for the named method according to this search order:

  1. Singleton methods in the receiver myobject

  2. Methods defined in myobject's class

  3. Methods defined among myobject's ancestors

If the method mymethod is not found, Ruby searches for a default method called method_missing. If this method is defined, it is passed the name of the missing method (as a symbol) and all the parameters that were passed to the nonexistent mymethod. This facility can be used for the dynamic handling of unknown messages sent at runtime.

1.4.4. Garbage Collection (GC)

Managing memory on a low level is hard and error prone, especially in a dynamic environment such as Ruby; having a garbage collection facility is a significant advantage. In languages such as C++, memory allocation and deallocation are handled by the programmer; in more recent languages such as Java, memory is reclaimed (when objects go out of scope) by a garbage collector.

Memory management done by the programmer is the source of two of the most common kinds of bugs. If an object is freed while still being referenced, a later access may find the memory in an inconsistent state. These so-called dangling pointers are difficult to track down because they often cause errors in code that is far removed from the offending statement. Memory leaks are caused when an object is not freed even though there are no references to it. Programs with this bug typically use up more and more memory until they crash; this kind of error is also difficult to find. Ruby uses a GC facility that tracks down unused objects and reclaims the storage that was allocated to them. For those who care about such things, Ruby's GC is done using a mark and sweep algorithm rather than reference counting (which frequently has difficulties with recursive structures).

Certain performance penalties may be associated with garbage collection. There are some limited controls in the GC module so that the programmer can tailor garbage collection to the needs of the individual program. We can also define an object finalizer, but this is an advanced topic (see section 11.3.14, "Defining Finalizers for Objects").




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net