Working in Advanced Programming Disciplines

 
   

Ruby Way
By Hal Fulton
Slots : 1.0
Table of Contents
 


Brother, can you paradigm?

Graffiti seen at IBM Austin, 1989

Many philosophies of programming are popular in various circles. These are often difficult to characterize in relation to object-oriented or dynamic techniques; and some of these styles can be actually independent of whether a language is dynamic or object-oriented.

Because we are far from experts in these matters, we are relying mostly on hearsay. So take these next paragraphs with a grain of sodium chloride.

Some programmers prefer a flavor of OOP known as prototype-based OOP (or classless OOP). In this world, an object isn't described as a member of a class. It is built from the ground up, and other objects are created based on the prototype. Ruby has at least rudimentary support for this programming style because it allows singleton methods for individual objects, and the clone method will clone these singletons. Interested readers should also look at the simple Ostruct class for building Python-like objects; and you should also be aware of how method_missing works.

One or two limitations in Ruby hamper classless OOP. Certain objects such as Fixnums are stored not as references, but as immediate values so that they can't have singleton methods. This is supposed to change in the future; but at the time of this writing, it's impossible to project when it will happen.

In functional programming (FP), emphasis is placed on the evaluation of expressions rather than the execution of commands. An FP language is one that encourages and supports functional programming, and as such, there is a natural gray area. Nearly all would agree that Haskell is a pure functional language, whereas Ruby certainly is not.

But Ruby has at least some minimal support for FP; it has a fairly rich set of methods for operating on arrays (lists), and it has Proc objects so that code can be encapsulated and called over and over. Ruby allows the method chaining that is so common in FP; although it is easy to be bitten by the phenomenon of a bang method (such as sort! or gsub!) that returns nil when the receiver doesn't actually change.

There have been some initial efforts at a library that would serve as a kind of FP compatibility layer, borrowing certain ideas from Haskell. At the time of this writing, these efforts aren't complete.

The concept of aspect-oriented programming (AOP) is an interesting one. In AOP, we try to deal with programming issues that crosscut the modular structure of the program. In other words, some activities or features of a system will be scattered across the system in code fragments here and there, rather than being gathered into one tidy location. We are attempting to modularize things that in traditional OOP or procedural techniques are difficult to modularize. We are working at right angles to our usual way of thinking.

Ruby certainly wasn't created specifically with AOP in mind. But it was designed to be a very flexible and dynamic language, and it is conceivable that these techniques can be facilitated by a library. In fact, there is a library called AspectR, which is an early effort at implementing AOP; see the Ruby Application Archive for the most recent version.

The concept of Design by Contract (DBC) is well-known to Eiffel devotees, although it is certainly known outside those circles as well. The general idea is that a method or class implements a contract; certain pre-conditions must be true if it is going to do its job, and it guarantees that certain post-conditions are true afterward. The robustness of a system can be greatly enhanced by the ability to specify this contract explicitly and have it automatically checked at runtime. The usefulness of the technique is expanded by the inheritance of contract information as classes are extended.

The Eiffel language has DBC explicitly built in; Ruby doesn't. There are at least two usable implementations of DBC libraries, however, and we recommend that you choose one and learn it.

Design patterns have inspired much discussion over the last few years. These, of course, are highly language-independent and can be implemented well in many languages. But again, Ruby's unusual flexibility makes them perhaps more practical than in some other environments. Well-known examples of these are given elsewhere; the Visitor pattern is essentially implemented in Ruby's default iterator each, and other patterns are part of Ruby's standard distribution (see delegator.rb and singleton.rb).

The Extreme Programming (XP) discipline is gaining devotees daily. This methodology encourages (among other things) early testing and refactoring on-the-fly.

XP isn't language specific, although it might be easier in some languages than others. Certainly we maintain that Ruby makes refactoring easier than many languages would, although that is a highly subjective claim. But the existence of the RubyUnit library is what makes for a real blending of Ruby and XP. This library facilitates unit testing; it is powerful, easy to use, and it has proven useful in developing other Ruby software in current use. We highly recommend the XP practice of testing early and often, and we recommend RubyUnit for those who want to do this in Ruby.

We should also mention Lapidary, another unit-testing framework created by XP fan Nathaniel Talbott (coming from a Smalltalk perspective). It can be found in the Ruby Application Archive.

By the time you read this, many of the issues we talk about in this section will have been fleshed out more. As always, your two best resources for the latest information are the comp.lang.ruby newsgroup and the Ruby Application Archive.

Working with Dynamic Features

Skynet became self-aware at 2:14 a.m. EDT August 29, 1997.

Terminator 2: Judgment Day

Many of you will come from the background of a very static language such as C. To those readers, we will address this rhetorical question: Can you imagine writing a C function that will take a string, treat it as a variable name, and return the value of the variable?

No? Then can you imagine removing or replacing the definition of a function? Can you imagine trapping calls to nonexistent functions? Or determining the name of the calling function? Or automatically obtaining a list of user-defined program elements (such as a list of all your functions)?

Ruby makes this sort of thing possible. This runtime flexibility, the ability to examine and change program elements at runtime, makes many problems easier. A runtime tracing utility, a debugger, and a profiling utility are all easy to write for Ruby and in Ruby. The well-known programs irb and xmp both use dynamic features of Ruby in order to perform their magic.

These abilities take getting used to, and they are easy to abuse. But the concepts have been around for many years (they are at least as old as LISP) and are regarded as tried and true in the Scheme and Smalltalk communities as well. Even Java, which owes so much to C and C++, has some dynamic features; so we expect this way of thinking to increase in popularity as time goes by.

Evaluating Code Dynamically

The global function eval compiles and executes a string that contains a fragment of Ruby code. This is a powerful (if slightly dangerous) mechanism because it allows you to build up code to be executed at runtime. For example, the following code reads in lines of the form name = expression; it then evaluates each expression and stores the result in a hash indexed by the corresponding variable name.

 

 parameters = { } ARGF.each do |line|   name, expr = line.split(/\s*=\s*/, 2)   parameters[name] = eval expr end 

Suppose that the input contained these lines.

 

 a = 1 b = 2 + 3 c = `date` 

Then the hash parameters would end up with the value { "a"=>1, "b"=>5, "c"=>"Mon Apr 30 21:17:47 CDT 2001\n"}. This example also illustrates the danger of evaling strings when you don't control their contents; a malicious user could put d=`rm *` in the input and ruin your day.

Ruby has three other methods that evaluate code on-the-fly: class_eval, module_eval, and instance_eval. The first two are synonyms, and all three do effectively the same thing; they evaluate a string or a block, but while doing so, they change the value of self to their own receiver. Perhaps the most common use of class_eval allows you to add methods to a class when all you have is a reference to that class. We used this in the hook_method code in the Trace example in the section "Tracking Changes to a Class or Object Definition." You'll find other examples in the more dynamic library modules, such as delegate.rb.

The eval method also makes it possible to evaluate local variables in a context outside their scope. We don't advise doing this lightly, but it's nice to have the capability.

Ruby associates local variables with blocks, with high level definition constructs (class, module, and method definitions), and with the top-level of your program (the code outside any definition constructs). Associated with each of these scopes is the binding of variables, along with other housekeeping details. Probably the ultimate user of bindings is irb, the interactive Ruby shell, which uses bindings to keep the variables in the program that you type separate from its own.

You can encapsulate the current binding in an object using the method Kernel#binding. Having done that, you can pass the binding as the second parameter to eval, setting the execution context for the code being evaluated.

 

 def aMethod   a = "local variable"   return binding end the_binding = aMethod eval "a", the_binding   # "local variable" 

Interestingly, the presence of a block associated with a method is stored as part of the binding, enabling tricks such as this:

 

 def aMethod   return binding end the_binding = aMethod {  puts "hello" } eval "yield", the_binding                # hello 

Removing Definitions

The dynamic nature of Ruby means that pretty much anything that can be defined can also be undefined. One conceivable reason to do this is to decouple pieces of code that are in the same scope by getting rid of variables once they have been used; another reason might be to specifically disallow certain dangerous method calls. Whatever your reason for removing a definition, it should naturally be done with caution because it can conceivably lead to debugging problems.

The radical way to undefine something is with the undef keyword (not surprisingly, the opposite of def). You can undef methods, local variables, and constants at the top level. Although a classname is a constant, you cannot remove a class definition this way.

 

 def asbestos   puts "Now fireproof" end tax = 0.08 PI = 3 asbestos puts "PI=#{ PI} , tax=#{ tax} " undef asbestos undef tax undef PI # Any reference to the above three # would now give an error. 

Within a class definition, a method or constant can be undefined in the same context in which it was defined. You can't undef within a method definition or undef an instance variable.

We also have the remove_method and undef_method methods available to us (defined in Module). The difference is slightly subtle: remove_method will remove the current (or nearest) definition of the method, whereas undef_method will literally cause the method to be undefined (removing it from superclasses as well). Listing 5.17 is an illustration of this.

Listing 5.17 Removing and Undefining Methods
 class Parent   def alpha     puts "parent alpha"   end   def beta     puts "parent beta"   end end class Child < Parent    def alpha      puts "child alpha"    end    def beta      puts "child beta"    end    remove_method :alpha   # Remove "this" alpha    undef_method :beta     # Remove every beta end x = Child.new x.alpha          # parent alpha x.beta           # Error! 

The remove_const method will remove a constant.

 

 module Math   remove_const :PI end # No PI anymore! 

Note that it is possible to remove a class definition in this way (because a class identifier is simply a constant).

 

 class BriefCandle   #... end out_out = BriefCandle.new class Object   remove_const :BriefCandle end # Can't instantiate BriefCandle again! # (Though out_out still exists...) 

Note that methods such remove_const and remove_method are (naturally enough) private methods. That is why we show them being called from inside a class or module definition rather than outside.

Obtaining Lists of Defined Entities

The reflection API of Ruby enables us to examine the classes and objects in our environment at runtime. We'll look at methods defined in Module, Class, and Object.

The Module module has a method named constants that returns an array of all the constants in the system (including class and module names). The nesting method returns an array of all the modules nested at the current location in the code.

The instance method Module#ancestors will return an array of all the ancestors of the specified class or module.

 

 list = Array.ancestors # [Array, Enumerable, Object, Kernel] 

The constants method will list all the constants accessible in the specified module. Any ancestor modules are included.

 

 list = Math.constants    # ["E", "PI"] 

The class_variables method will return a list of all class variables in the given class and its superclasses. The included_modules method will list the modules included in a class.

 

 class Parent   @@var1 = nil end class Child < Parent   @@var2 = nil end list1 = Parent.class_variables   # ["@@var1"] list2 = Array.included_modules   # [Enumerable, Kernel] 

The Class methods instance_methods and public_instance_methods are synonyms; they return a list of the public instance methods for a class. The methods private_instance_methods and protected_instance_methods behave as expected. Any of these can take a Boolean parameter, which defaults to false; if it is set to true, superclasses will be searched as well, resulting in a larger list.

 

 n1 = Array.instance_methods.size                # 66 n2 = Array.public_instance_methods.size         # 66 n3 = Array.private_instance_methods.size        # 1 n4 = Array.protected_instance_methods.size      # 0 n5 = Array.public_instance_methods(true).size   # 106 

The Object class has a number of similar methods that operate on instances (see Listing 5.18). methods and public_methods are synonyms that return a list of publicly accessible methods. The methods private_methods, protected_methods, and singleton_methods all behave as expected.

Listing 5.18 Reflection and Instance Variables
 class SomeClass   def initialize     @a = 1     @b = 2   end   def mymeth     #...    end    protected :mymeth  end  x = SomeClass.new  def x.newmeth    # ...  end  iv = x.instance_variables        # ["@b", "@a"]  meth = x.methods.size            # 37  pub  = x.public_methods.size     # 37  pri  = x.private_methods.size    # 66  pro  = x.protected_methods.size  # 1  sm   = x.singleton_methods.size  # 1 

Note that none of the preceding ever takes a parameter.

Examining the Call Stack

And you may ask yourself:

Well, how did I get here?

Talking Heads, "Once in a Lifetime"

Sometimes we want to know who our caller was. This could be useful information if, for example, we had a fatal exception. The caller method, defined in Kernel, makes this possible. It returns an array of strings in which the first element represents the caller, the next element represents the caller's caller, and so on.

 

 def func1   puts caller[0] end def func2   func1 end func2              # Prints: somefile.rb:6:in `func2' 

The string is in the form file;line or file:line: in method, as shown previously.

Monitoring Execution of a Program

A Ruby program can introspect or examine its own execution. There are many applications for such an ability; the interested reader can refer to the sources debug.rb, profile.rb, and tracer.rb. It is even conceivable to use this facility in creating a design-by-contract (DBC) libraryalthough the most popular one at the time of this writing doesn't use this technique.

The interesting thing is that this trick is implemented purely in Ruby. We use the Ruby method set_trace_func, which allows you to invoke a block whenever significant events happen in the execution of a program. A good reference will show the calling sequence for set_trace_func, so we'll just show a simple example here.

 

 def meth(n)   sum = 0   for i in 1..n     sum += i   end   sum end set_trace_func proc do |event, file, line,                         id, binding, klass, *rest|   printf "%8s %s:%d  %s/%s\n", event, file, line,                                klass, id end meth(2) 

This produces the output:

 

         line prog.rb:13  false/         call prog.rb:1  Object/meth         line prog.rb:2  Object/meth         line prog.rb:3  Object/meth       c-call prog.rb:3  Range/each         line prog.rb:4  Object/meth       c-call prog.rb:4  Fixnum/+ c-return prog.rb:4  Fixnum/+     line prog.rb:4  Object/meth   c-call prog.rb:4  Fixnum/+ c-return prog.rb:4  Fixnum/+ c-return prog.rb:4  Range/each     line prog.rb:6  Object/meth   return prog.rb:6  Object/meth 

Another related method is Kernel#trace_var, which invokes a block whenever a global variable is assigned a value.

Suppose that you want to trace the execution of a program from outside, strictly as an aid in debugging. The simplest way to see what a program is doing is to use the tracer library that we mentioned previously. Given a program prog.rb:

 

 def meth(n)   (1..n).each { |i| puts i} end meth(3) 

You can simply load tracer from the command line:

 

 % ruby -r tracer prog.rb #0:prog.rb:1::-:     def meth(n) #0:prog.rb:1:Module:>:     def meth(n) #0:prog.rb:1:Module::     def meth(n) #0:prog.rb:2:Object:-:       sum = 0 #0:prog.rb:3:Object:-:       for i in 1..n #0:prog.rb:3:Range:>:       for i in 1..n #0:prog.rb:4:Object:-:         sum += i #0:prog.rb:4:Fixnum:>:         sum += i #0:prog.rb:4:Fixnum::         sum += i #0:prog.rb:4:Fixnum: 

The lines output by tracer show the thread number, the filename and line number, the class being used, the event type, and the line from the source file being executed. The event types include '-' when a source line is executed, '>' for a call, '<' for a return, 'C' for a class, and 'E' for an end.

Traversing the Object Space

The Ruby runtime system needs to keep track of all known objects (if for no other reason than to be able to garbage-collect those no longer referenced). This information is made accessible via the ObjectSpace.each_object method.

 

 ObjectSpace.each_object do |obj|   printf "%20s: %s\n", obj.class, obj.inspect end 

If you specify a class or module as a parameter to each_object, only objects of that type will be returned.

The ObjectSpace module is also useful in defining object finalizers (see the section "Defining Finalizers for Objects").

Handling Calls to Nonexistent Methods

Sometimes it's useful to be able to write classes that respond to arbitrary method calls. For example, you might want to wrap calls to external programs in a class, providing access to each program as a method call. You can't know ahead of time the names of all these programs, so you can't create the methods as you write the class. Here comes Object#method_missing to the rescue. Whenever a Ruby object receives a message for a method that isn't implemented in the receiver, it invokes the method_missing method instead. You can use that to catch what would otherwise be an error, treating it as a normal method call. Let's implement the operating system CommandWrapper class:

 

 class CommandWrapper   def method_missing(method, *args)     system(method.to_s, *args)   end end cw = CommandWrapper.new cw.date                   # Sat Apr 28 22:50:11 CDT 2001 cw.du '-s', '/tmp'        # 166749  /tmp 

The first parameter to method_missing is the name of the method that was called (and that couldn't be found). Whatever was passed in that method call is then given as the remaining parameters.

If your method_missing handler decides that it doesn't want to handle a particular call, it should call super, rather than raising an exception. That allows method_missing handlers in superclasses to have a shot at dealing with the situation. Eventually, the method_missing method defined in class Object will be invoked, and that ends up raising an exception.

Tracking Changes to a Class or Object Definition

Perhaps we should start this by asking: Who cares? Why are we interested in tracking changes to classes?

One possible reason is that we're trying to keep track of the state of the Ruby program being run. Perhaps we're implementing some kind of GUI-based debugger, and we need to refresh a list of methods if our user adds one on-the-fly.

Another reason might be that we're doing clever things to other classes. For example, say that we wanted to write a module that could be included in any class definition. From then on, any call to a method in that class will be traced. We might use it something like this:

 

 class MyClass   def one   end   include Trace   def two(x, y)   end end m = MyClass.new m.one                 # one called. Params = m.two(1, 'cat')       # two called. Params = 1, cat 

It will also work for any subclasses of the class we're tracing:

 

 class Fred < MyClass   def meth(*a)   end end Fred.new.meth(2,3,4,5)   # meth called. Params = 2, 3, 4, 5 

We could implement this module as shown in Listing 5.19.

Listing 5.19 Trace Module
 module Trace   def Trace.append_features(into)     into.instance_methods.each {  |m| Trace.hook_method(into, m) }     def into.method_added(method)       unless @adding         @adding = true         Trace.hook_method(self, method)         @adding = false        end      end      super    end    def Trace.hook_method(klass, method)      klass.class_eval <<-EOD      alias :old_#{ method}   :#{ method}      def #{ method} (*args)        puts "#{ method}  called. Params = #{ args.join(', ')} "        old_#{ method} (*args)      end      EOD    end end 

This code has two main methods. The first, append_features, is a callback invoked whenever a module is inserted into a class. Our version does two things. It calls hook_method for every method that's already been defined in the target class, and it inserts a definition for method_added into that class. This means that any subsequently added method will also be detected and hooked.

The hook itself is pretty straightforward: When a method is added, it is immediately aliased to the name old_name. The original method is then replaced by out tracing code, which dumps out the method name and parameters before invoking the original method.

To detect the addition of a new class method to a class or module, we can define a class method singleton_method_added within that class. (Recall that a singleton method in this sense is what we usually refer to as a class method because Class is an object.) This method comes from Kernel and by default does nothing, but we can make it behave as we prefer.

 

 class MyClass   def MyClass.singleton_method_added(sym)     puts "Added method #{ sym.id2name}  to class MyClass."   end   def MyClass.meth1     puts "I'm meth1."   end end def MyClass.meth2   puts "And I'm meth2." end 

The output we get from this is as follows:

 

 Added method singleton_method_added to class MyClass. Added method meth1 to class MyClass. Added method meth2 to class MyClass. 

Note that there are actually three methods added here. Perhaps contrary to expectation, singleton_method_added is able to track its own addition to the class.

The inherited method (from Class) is used in much the same way. It is called whenever a class is subclassed by another.

 

 class MyClass   def MyClass.inherited(subclass)     puts "#{ subclass}  inherits from MyClass."   end   # ... end class OtherClass < MyClass   # ... end # Output: OtherClass inherits from MyClass. 

We can also track the addition of a module's instance methods to an object (done via the extend method). The method extend_object is called whenever an extend is done.

 

 module MyMod   def MyMod.extend_object(obj)     puts "Extending object id #{ obj.id} , type #{ obj.type} "     super   end   # ... end x = [1, 2, 3] x.extend(MyMod) # Output: # Extending object id 36491192, type Array 

Note that the call to super is needed in order for the real extend_object method to do its work. This is analogous to the behavior of append_features (see the section "Working with Modules"). This method can also be used to track the usage of modules.

Defining Finalizers for Objects

Ruby classes have constructors (the methods new and initialize) but don't have destructors (methods that delete objects). That's because Ruby uses mark-and-sweep garbage collection to remove unreferenced objects; a destructor would make no sense.

However, people coming to Ruby from languages such as C++ seem to miss the facility, and often ask how they can write code to handle the finalization of objects. The simple answer is that there is no real way to do it reliably. But you can arrange to have code called when an object is garbage-collected.

 

 a = "hello" puts "The string 'hello' has an object id #{ a.id} " ObjectSpace.define_finalizer(a) {  |id| puts "Destroying #{ id} " } puts "Nothing to tidy" GC.start a = nil puts "The original string is now a candidate for collection" GC.start 

This produces the following output:

 

 The string 'hello' has an object id 537684890 Nothing to tidy The original string is now a candidate for collection Destroying 537684890 

Note that by the time the finalizer is called, the object has basically been destroyed already. An attempt to convert the ID you receive back into an object reference using ObjectSpace._id2ref will raise a RangeError, complaining that you are attempting to use a recycled object.

However, all this might be moot. There's a style of programming in Ruby that uses blocks to encapsulate the use of a resource. At the end of the block, the resource is deleted and life carries on merrily, all without the use of finalizers. For example, consider the block form of File.open:

 

 File.open("myfile.txt") do |aFile|   l1 = aFile.read   # ... end 

Here the File object is passed into the block. When the block exits, the file is closed, all under control of the open method. If you wanted to write a subset of File.open in Ruby (for efficiency, it's currently written in C as part of the runtime system), it might look something like this:

 

 def File.open(name, mode = "r")   f = os_file_open(name, mode)   if block_given?     begin       yield f     ensure       f.close     end     return nil   else     return f   end end 

The routine tests for the presence of a block. If found, it invokes that block, passing in the open file. It does this in the context of a begin-end block, ensuring that it will close the file after the block terminates, even if an exception is thrown.

Dynamically Instantiating a Class by Name

We have seen this question more than once. Given a string containing the name of a class, how can we create an instance of that class?

The answer is annoyingly simple. Use eval for the purpose.

 

 classname = "Array" classvar = eval(classname) x = classvar.new(4, 1)        # [1, 1, 1, 1] 

As always, make sure that the string you're evaluating is a safe one.


   

 

 



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

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