August 9, 2007
@ 12:01 AM
I've been taking a look at ruby for a few month, I am finally getting to a stage where (I think/hope) I can actually say something intelligent about it. Last time I had an "aha moment" about a language was the fist time I saw Java.  C++  was oh-so-powerful, but Java was (is)  much more elegant and nice. Now Ruby changes the rules of the game again.
When I first heard about ruby I thought it was just a fad, something that the cool kids are using but its just another language. I getting more and more convinced that it isn't so. I am trying not to get too "silvery-bulletey"  here but working in ruby seems to actually increase productivity.
Let's take Dependency Injection(DI) as an example. DI is one of the most important and powerful tool I've learned in regard to Object Oriented development. Instead of classes depending directly on other classes classes depends on interfaces. And external classes (assemblers) provide them with their dependencies. This allows for loose coupling, increase testability and a lot of other such goodies (you can read a concise explanation in a paper I wrote on OO principles or get a more thorough explanation in a paper Martin Fowler wrote on "Inversion of Control Containers and the Dependency Injection pattern".  The .NET and Java worlds are filled with a lot of frameworks to help solve this elegantly. Spring (and Spring.Net) is probably the most known one.

How do you do DI in Ruby? in two words - you don't
If I am to join the "Define DI in one sentence" challenge by Jim Weirich I would say that
"DI is a powerful and good workaround to  the collaboration coupling problem between objects which is best addressed at the language level"

Why doesn't ruby need DI?

Well, I would say that it all starts at the basics. I remember when I learned OO, I was told objects communicate using messages. I never really understood why they call "method invocation" messages - it doesn't make any sense. The point is that in ruby you really don't "call a method" you "send a message"*.  When you make a call like someVariable.SomeMessage - the ruby interpreter  doesn't really care about the type of someVairable just that the object it holds (and everything is an object) has some entry which can handle SomeMessage.
Let's start with a simple example
consider the following code:

class Foo
  def bar
    puts "Foo-bar"
  end
end

class Foosa < Foo
end

class Baruser
  def baruse(b)
    b.bar  #dependency
  end
end

bu= Baruser.new

bu.baruse(Foo.new)
bu.baruse(Foosa.new)   # sub-class

Well, nothing particularly exciting here. if you run this  you get foo-bar printed twice. That's very much like the dependency injection you see in .NET or Java
It gets a little more interesting when we consider that the following classes would all work as well

class Foz
  def bar
    puts "Foz-bar"
  end
end

class NoBar
  def method_missing(methodname, *args)
    puts "NoBar" if "bar" == methodname.to_s
  end
end

class MakeBar
   define_method(:bar) {puts "madebar"}
end

bu= Baruser.new

bu.baruse(Foz.new)     # another type altogether
bu.baruse(NoBar.new)   # a class that doesn't have bar method
bu.baruse(MakeBar.new) # a class where the bar method is created programatically
 
We can see from the examples that what ruby does is searching for a handler for the bar message. The handler can be a method (symbol) called bar or a generic handle like Missing_Method - ruby doesn't care as long as the message get handled

I think that's pretty nice, we have a lot of flexibility on the dependency side but the depending class still  essentially gets the dependency by injection (the call to baruse)
Well, ruby can help us flex the dependent side as well. The answer is in the last example which uses (an overly simple an uninteresting example of) meta-programming Consider the following code example using Mocha which is a Mock object library for ruby
 
Lets say we modify our Baruser class to the following.

class Baruser
  def initialize
    buildfoo()
  end
  def baruse
    foo = buildfoo()
    foo.bar  #dependency
  end
 
 private 
  def buildfoo
    Foo.new #dependency
  end
end

Note that buildfoo is private - if we wanted to test this in.NET we need to have Foo around. i.e. we can no longer test Baruser by itself
if we use Mocha in ruby we can do the following:
foo = mock('foo')
foo.expects(:bar).at_least_once.

bu = Baruser.new
bu.stubs(:buildfoo).returns(foo) # basically what happens here is that the instance we have is changed
bu.baruse

I can't believe they invented it  - You can get  ruby for just the price of download and if you call within the next 15 minutes we'll throw in a copy of gems free of charge :)

In any event what we see here that using meta-programming and other ruby constructs we can forgo using DI altogether - no wonder Neil Ford defined DI as :
"Dependency Injection enables a vitally important but nevertheless weak, limited, syntactically confounding, and dauntingly complex form of one of the kinds of meta-programming that should exist in the language."
 Not to mention that the resulting code is much more elegant. which is actually what I like best about ruby, the code is much cleaner (but that's for another post)

Some closing thoughts

  • .NET 3.5 bring some of the ruby goodness to C# - but as the previous post demonstrated it is just a move in the right direction but not the whole thing
  • While I am on the subject of the previous post the whole interface vs. class thing is, of course, a moot point in Ruby since there are no interfaces. Interfaces, like DI, are another thing that is very important in C# and Java and not needed in Ruby
  • I've read some complaints on Ruby's performance. performance is important but there are two things to remember. First, the fact that a solution is not the fastest doesn't mean that it isn't fast enough. Second, I can still vividly recall the performance benchmarks for our Java code before we got the first hot spot compiler installed. The point is, that if it is important to enough people it will get better.




* I know, I know, Smalltalk had it since the beginning of time. However in Smalltalk everything is an object, in ruby you can also write plain scripts and (more importantly for me) - I never really took more than a cursory look at smalltalk  so I never  saw that.

 


 
Thursday, August 09, 2007 8:13:45 PM (GMT Standard Time, UTC+00:00)
I think I prefer interfaces because once an interface is defined it is easier to add new implementations without actually reading the code that uses it. Refactoring and renaming is also easier with interfaces. But I guess this boils down to the old dynamic vs static languages...
Yoni
Friday, August 10, 2007 8:24:31 AM (GMT Standard Time, UTC+00:00)
Hi Yoni,
Thanks for the comment.
Yes - but you can also do that with an abstract class (and not have all the classes using it break) and inheritance (in any language)Also in Ruby you can extend a class or an instance without actually reading the code

I am note sure why you say refactoring is easier

Arnon
Friday, August 10, 2007 12:18:44 PM (GMT Standard Time, UTC+00:00)
Hi Arnon,

What I meant was that in C# I can have an interface (or abstract class) like this:

interface Foo
{
void Bar();
}

And also a method somewhere that accepts Foo as an argument.
Now whenever I find the need for a new implementation of Foo all I have to do is implement the interface - the compiler (and the refactoring tool before it) will help me make sure I have implemented all the methods required by the interaction.

In a dynamic language I have to send in an object that implements the required methods and there is no way of doing that without looking for method invocations made on that object (or searching the code for the right class to inherit from). Well, I can simply wait for a runtime error telling me I haven't implemented a required method but I think that's quite an annoying task (think about implementing IList this way).

Refactoring is also problematic here because there is no way for a refactoring tool to match a method invocation (or sending a message) with the target method, so automatic renaming of methods or cascading deletion of a redundant method are not possible.

I think extending classes and instances in scripting languages have only added unneeded complexity in the guise of empowering developers. There is a good reason why class definitions should reside in a single location and why objects should behave as black boxes rather than a collection of properties and methods.
Yoni
Sunday, August 12, 2007 7:01:57 AM (GMT Standard Time, UTC+00:00)
Hi Yoni,
The main reason for interfaces is to replace type coupling with contract coupling while overcoming the limitation of single inheritance. under the assumption that contract doesn't change as ofter as types. Dynamic languages such as Ruby lift this barrier. The other "features" are side effect
As for dynamic languages
First - If you really want to you can write in the same style in Ruby by using modules with empty implementations and mixins (include) and IDEs such as Netbeans will give you the intelitype behavior etc.- though I am not sure there's a good reason to do so

Second newer IDEs give you refactoring and other such niceties - but I (at least) don't use interfaces to learn about implementation since, unless it is a trivial implementation, interfaces don't give you enough understanding about how to actually use something. I read tests, examples and documentation for that.

Extending classes is coming to .Net and I am sure Java will also adopt it (as it did with generics and attributes (annotations)) - not to mention partial classes - single location for objects is far from being just a "scripting languages" thing ...

Arnon
Sunday, August 12, 2007 12:55:51 PM (GMT Standard Time, UTC+00:00)
"Dynamic languages such as Ruby lift this barrier"

I think that creates an illusion of having no coupling at all when in fact there is contract coupling. Refactorings such as "rename method" and operations such as "find all references" may seem like a mere nicety but actually derive from the fact that in strongly typed languages - there is a single, authoritative, definition to any message (method) which resides in the interface (or base class) definition.

In dynamic languages, however, every message is defined at least twice (once where it is sent and once where it is received) and usually more if there is any polymorphism involved - resulting in a form of duplication which immediately lowers maintainability.

Just imagine all your Java or C# method calls written using reflection. Don't you find it a maintenance nightmare?

Regarding the issue of extending classes (and partial classes) I am very sorry it was ever invented. Developers should be encouraged to divide large classes by decomposing them into decoupled sub-components using design patterns and not by simply splitting them between files. Even though a feature is called "extending" a class it is in violation of the Open Closed Principle...
Yoni
Comments are closed.