Quick Start

This page gives a quick overview of some of Diamondback Ruby’s (DRuby’s) features. For more comprehensive documentation, see the manual.

Installation

The easiest way to install DRuby is to use the RubyGems package manager. To install DRuby, first download the gems for your platform and operating system from the download page. Then install it using the “gem install” command. For example:

   $ gem install diamondback-ruby-0.20090415-powerpc-darwin-9.gem
   Building native extensions.  This could take a while…
   Successfully installed diamondback-ruby-0.20090415-powerpc-darwin-9
   1 gem installed
Be sure the RubyGem bin directory is in your path. If it is not, you should get a warning message of with the above command.


A small example

Type the code into your favorite text editor / IDE and save it as “duck1.rb”.

1 class Duck
2   def quack() 
3     "quack!" 
4   end
5 end
6 d = Duck.new
7 puts d.quack

Now lets run DRuby on the program.

$ druby duck1.rb

DRuby analysis complete.

As expected, DRuby did produce any type errors for this simple program. Lets introduce an error to see what happens. Change the last line of duck1.rb to:

7 puts d.qauck
and save it as “duck2.rb”. Now, if we run DRuby, it produces the output:

$ druby duck2.rb
[ERROR] instance Duck does not support methods qauck
  in typing method call d.qauck
  at ./duck2.rb:7 
  in typing ::Duck.new
  at ./duck2.rb:6 
 
DRuby analysis complete.

Note that in the error message, DRuby prints not only the location of the errant method call, but also the location of the instantiation of the Duck class that was stored in the variable d.


More on Duck Typing

In Ruby, when we write the expression d.quack, there is nothing that requires the variable d to be an instance of the Duck class. Instead, it can be an instance of any class so long as that class has a quack method. This style of subtyping is commonly referred to as duck typing. DRuby’s analysis was designed around this philosophy and therefore supports many idioms associated with duck typing.

Lets try a slightly larger example:

 1 class Duck
 2   def quack() 
 3     "quack!" 
 4   end
 5   def fly!()
 6     @in_flight = true
 7   end
 8 end
 9 class GeneticallyEngineeredCow
10   def quack()
11     "moo-ack!"
12   end
13   def tip!()
14     @tipped = true
15   end
16 end
17 
18 ducks = [Duck.new, GeneticallyEngineeredCow.new]
19 ducks.each {|d| d.quack}

Here, we have defined two classes, Duck and GeneticallyEngineeredCow which each respond to the quack method. However only ducks can fly, and only cows may be tipped. On line 18, we create the array ducks which contains an instance of each class. Finally, on line 19, we iterate over the array using each calling quack on each element.

When we run DRuby on this code, it does not produce any type errors. This is because while Ducks and GeneticallyEngineeredCows each have different methods, only the quack method is called and DRuby verifies that both classes stored in the array respond to this method. If the last line where changed to

19 ducks.each {|d| d.fly!}

DRuby would report the error:

[ERROR] instance GeneticallyEngineeredCow does not support methods fly!
  in typing method call d.fly!
  at ./duck4.rb:19 
  in typing method call ducks.each
  at ./duck4.rb:19 
  in typing expression [_, _]
  at ./duck4.rb:18 
  in typing ::GeneticallyEngineeredCow.new
  at ./duck4.rb:18 
 
DRuby analysis complete.


Annotations

While DRuby is able to infer types for many Ruby idioms, it sometimes needs a little help for more advanced features, like higher order polymorphism and modeling runtime type tests. Therefore, DRuby includes an annotation language that was inspired by the RDoc documentation system. Annotations are useful documentation in and of themselves, so any method may be documented using this syntax. Here are a few examples:

 1 ##% quack : () -> String
 2 def quack()
 3   "quack!"
 4 end
 5 
 6 ##% eat : Food -> NilClass
 7 def eat(food) 
 8   # nom nom
 9 end
10 
11 ##% color? : Symbol -> Boolean
12 ##% color? : Fixnum -> Boolean
13 def color?(arg)
14   case arg
15   when Symbol: arg == :brown || arg == :white
16   when Fixnum: arg > 0 && arg < 42
17   end
18 end

Monomorphic annotations such as those on lines 1 and 6 are checked statically by DRuby: if the method does not match the signature, it will produce an error when analyzed.

More complex annotations, such as the intersection type on lines 11-12, are currently checked dynamically: we insert code into the Ruby program to detect a type error when the program is executed. This ensures that no statically analyzed code produces a runtime error if this dynamically checked code turns out to be incorrect. This feature is still a little rough around the edges, and so we don’t have much documentation on using it just yet. Check back soon for more details!

While using dynamic checks to verify ruby code is somewhat unsatisfying, we believe it provides a nice way for a program to incrementally benefit from static typing. We can’t possibly statically verify every (correct) Ruby program (it’s undecidable), so this technique provides a back door to force DRuby to accept code it is otherwise unable to analyze, without sacrificing the safety of the rest of its analysis.

We are working on improving DRuby’s analysis to statically check these annotations against their method bodies by recognizing some Ruby constructs specially. For instance, in the above example, DRuby would need to precisely model the case statement on lines 14-17 to ensure the code properly used the argument after testing its type. Unfortunately, there are a myriad of ways to perform such a test in Ruby, and we are working on striking the proper balance between modeling the kind of code programmers tend to write without making the analysis too difficult to understand or predict.


Metaprogramming

Ruby includes several dynamic features, such as the eval method, which are difficult to analyze statically. For example, consider this example which defines get/set methods for “color” and “weight” using eval:

 1 class Duck
 2   def initialize()
 3     @desc = {}
 4   end
 5 
 6   Attributes = %w(color weight)
 7   Attributes.each do |attrib|
 8     eval "def #{attrib}() @#{attrib} end"
 9     eval <<-EOF
10       def #{attrib}=(x)
11         @#{attrib} = x
12         @desc[#{attrib.inspect}] = x
13       end
14     EOF
15   end
16       
17   def describe() 
18     puts "This is a duck"
19     @desc.each {|k,v| puts "Its #{k} is #{v}"}
20   end
21 end
22 
23 d = Duck.new
24 d.color = "blue"
25 d.describe
26 
27 if ARGV.length == 17
28   d.height = 20
29 end

If we were to run this program using the standard Ruby interpreter, we would get the following output:

$ ruby duck_eval.rb
This is a duck
Its color is blue

Note that this program contains a type error: the method call on line 28 is incorrect since the Duck class never defines the height= method. When we ran the program, we did not reach that line of code, and so we don’t know that an error is lurking there.

If we run DRuby on this program, it produces several errors:

[ERROR] instance Duck does not support methods color=
  in typing method call d.color=
  at ./duck_eval.rb:24 
  in typing ::Duck.new
  at ./duck_eval.rb:23 

[ERROR] instance Duck does not support methods height=
  in typing method call d.height=
  at ./duck_eval.rb:28 
  in typing ::Duck.new
  at ./duck_eval.rb:23 

DRuby analysis complete.

While DRuby did detect the real error, it also produced an error for the correct call to color= which was defined using eval. A programmer looking at this output may not look at the code closely and just assume both errors are due to DRuby not being able to handle the eval. In order to model such code, we can instruct DRuby to execute the program internally, watching for calls to eval and seeing what they do. Since running the above code will always execute the eval method (it lives at the top level of the class definition), DRuby will learn about the method definitions for color and weight and analyzes the program as if they were present in the code. This is done by using the —dr-profile command line argument:

$ druby —dr-profile duck_eval.rb 
[ERROR] instance Duck does not support methods height=
  in typing method call d.height=
  at /Users/mike/research/projects/ruby/web/examples/duck_eval.rb:28 
  in typing ::Duck.new
  at /Users/mike/research/projects/ruby/web/examples/duck_eval.rb:23 

DRuby analysis complete.

DRuby no longer complains about the call to color= since it knows that method exists at runtime, and now only complains about the actual error. While somewhat simple, we believe this technique should work well in practice since most uses of dynamic features appear to follow this same pattern: if they are going to perform a side effect like adding a method, they will do so on every execution. We can also use the program test suite to teach DRuby about additional occurrences of dynamic constructs that may not be present on every run. More details of how to use this feature will be available soon (you can also read our tech report for more formal details about this technique).

Overview

Download

Documentation