Skip to main content

Crossing borders: Delayed binding

The simplicity, flexibility, and power in dynamic languages

Bruce Tate (bruce.tate@j2life.com), President, RapidRed
Bruce Tate
Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's a Java Champion and the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby and Rails: Up and Running. He spent 13 years at IBM and is now the founder of the RapidRed consultancy, where he specializes in lightweight development strategies and architectures based on Ruby and the Ruby on Rails framework. His practice now offers a full range of Ruby and Rails development, education, consulting, and mentoring offerings.

Summary:  Statically typed languages, such as the Java™ language and C, bind a method call to their implementation at compile time. This strategy lets these languages perform a wide variety of syntax and type checks, giving them more stability -- and often better performance -- than dynamically typed languages that have no such compile-time checks. But static typing comes with a serious limitation: early binding. Some dynamic languages -- such as Ruby, Smalltalk, and Self -- allow delayed binding, which enables another level of programming features.

View more content in this series

Date:  07 Nov 2006
Level:  Intermediate
Activity:  2066 views

A few years ago, I had the pleasure of teaching my oldest daughter to ski. One of the tools provided by ski school was a tiny rope that ties the tips of the skis together. With this rope, a new skier can easily make the ideal formation for techniques such as turning, slowing down, and stopping. Initially, skiers depend on the rope. My daughter swears she'll never ski without it. Of course, she doesn't have the whole picture yet. That's okay. I know she'll eventually want to do things with her skis that force her to remove the restraint.

About this series

In the Crossing borders series, author Bruce Tate advances the notion that today's Java programmers are well served by learning other approaches and languages. The programming landscape has changed since Java technology was the obvious best choice for all development projects. Other frameworks are shaping the way Java frameworks are built, and the concepts you learn from other languages can inform your Java programming. The Python (or Ruby, or Smalltalk, or ... fill in the blank) code you write can change the way that you approach Java coding.

This series introduces you to programming concepts and techniques that are radically different from, but also directly applicable to, Java development. In some cases, you'll need to integrate the technology to take advantage of it. In others, you'll be able to apply the concepts directly. The individual tool isn't as important as the idea that other languages and frameworks can influence developers, frameworks, and even fundamental approaches in the Java community.

As a Java developer, I had a similar experience. I loved the added security that static types provide. I debated with Dave Thomas, of Ruby fame, on the benefits of static type checking, but I couldn't be swayed. Skiing is a radically different experience without that initial safety harness. It turns out that for many, static typing is a crutch that's no different from that ski restraint. To Dave, I just didn't have enough experience to appreciate the benefits of dynamic languages. As I've come to appreciate Ruby and Smalltalk, I'm beginning to understand what I'm leaving behind with dynamic typing, but I also better understand the benefits. (See this previous Crossing borders article for a general comparison of static and dynamic typing strategies.) The biggest benefit to me is delayed binding. This article explores the advantages of delayed binding, using programming examples in Ruby, Smalltalk, and a Smalltalk derivative called Self.

Late and early binding

Programming languages can separate the declaration of functions (or methods in an object-oriented language) from their invocation. You declare a method and invoke it using separate syntax, but eventually the system needs to tie the two together. The process of wiring the invocation up to the implementation is called binding. Whether you bind early or late first to the type, and second to the implementation, has a dramatic impact on the programming experience for a given language. Most object-oriented languages bind to the implementation late to allow polymorphism, the feature that lets you represent many different subtypes as one type. Java code and C primarily bind to a type early, within a compile step. Using this strategy, the compiler has enough information to catch many different types of bugs, such as type incompatibility of method arguments or return values. For example, the code in Listing 1 generates several compile errors:


Listing 1. Compile time binding
...
  x = 1.0 + anObject.methodThatReturnsString();
  anObject.methodRequiringIntParameter(aString);
...

Early binding can help you detect problems early, through compile-time errors. The value of this kind of assistance is fiercely debated. It's clear that type checking isn't enough to prove that your code is correct. You must test, and this kind of testing usually catches problems related to type. But this assistance comes at cost. With early binding, a compiler must make assumptions about the structure of the target object:

  • There must be a finite, predefined list of methods.
  • The method that you want to invoke must exist at compile time.
  • You must bind to known types, interfaces, or common superclasses.
  • The methods and attributes of your binding target must be fixed.

These assumptions might not seem restrictive at first, but if you drill a little deeper, you can quickly come up with scenarios that could benefit from later binding, including some you've seen in the Crossing borders series:

  • Active Record, the persistence framework for Ruby on Rails, has objects with no compile-time attributes. After making a database connection, Rails adds an attribute to the model -- at run time -- for each row in the database. Active Record users do not always need to change model objects as the data model changes. Late binding makes this structure possible.

  • Unit testers must often write code that binds test implementations, such as stubs or mocks, and production implementations to the same method invocation. Java developers often use a design pattern called dependency injection, often requiring complex frameworks such as EJB or Spring, to solve this problem. These frameworks deliver extraordinary benefits, but they also incur significant complexity costs. Aspect-oriented programming or object factories with dependency lookup also solve the problem, but again at the cost of increased complexity. Testing frameworks in dynamic languages such as Smalltalk and Ruby do not need dependency-injection frameworks because they can make a run-time choice and bind to the desired implementation. They can often accomplish the same goals with a fraction of the code.

  • The whole Smalltalk language is built on the premise of delayed binding. Smalltalk developers build onto a continuously running application called the image. Because the image is always running, any addition, deletion, or update of a method in any class occurs at run time. Delayed binding lets Smalltalk applications keep running throughout the development cycle.

A continuum

Static and dynamic are points on a continuum. Some languages are highly static. The Java language is more dynamic than, say, C or C++. Each point in the continuum has its own set of trade-offs. The Java language has several features -- all with relatively high complexity costs -- that help to delay binding. Reflection, dependency injection, and XML configuration are used to delay binding and reduce coupling. Over time, the Java language is getting more dynamic through the addition of features such as aspect-oriented programming. You might begin to think Java developers have everything they need. But a class of languages -- such as Smalltalk, Self, and Ruby -- are even more dynamic and allow even better ways to delay binding.

They provide techniques that the Java language doesn't, such as overriding the behavior that happens when a method is missing. Remember, the Java language requires methods to exist for compile-time binding. Other languages allow open classes -- classes that can change based on a developer's needs. When you look long and hard at the evolution of frameworks, you find an increasing need for delayed binding, leading to increasingly unnatural bolt-ons in the Java language that complicate and obfuscate the language. Meanwhile, other languages sit ready and waiting for us to build just the sorts of frameworks that can lead to radically higher levels of abstraction, and much better productivity. The benefit to you is clear: you have a language that's more expressive and more productive.

To illustrate the points on a continuum, consider reflection. With the Java language, you can load a class at run time, find a method through reflection, identify the correct parameter set for the method, and then execute that method. You'd probably write a couple of dozen lines of code to do so. But the effort to delay binding is not proportional to the payoff, so most Java application developers don't use the techniques. Ruby, Smalltalk, and Self all use a primitive operation -- such as object.send(method_name) in Ruby -- to do the same thing trivially. You see many examples of this technique changing the very nature of programming in those languages.

The more you dig into type and binding strategies, the more you find that waiting until run time to bind to an invocation or type fundamentally changes the programming process, opening a whole new world of possibilities. True, you find less safety. But you also find less repetition, more power, and more flexibility with fewer lines of code. To understand how it all works, I'm going to take some quick glances at Smalltalk, Self, and Ruby. I'll first look at invoking a method with delayed binding, and then I'll show techniques you can use to change the definition of a class at run time.


Delaying invocation

You've seen that with static languages, the compiler directly binds an invocation to an implementation at compile time. Dynamic languages work a little differently. Ruby, Smalltalk, and Self rely on message passing to delay binding. Using message passing, a client object specifies a target object, a message, and a set of arguments. It is entirely a run-time mechanism. So dynamic languages effectively add a level of indirection. Rather than binding from invocation to type to implementation, they bind a message name to an object. Then, they bind that object to a name or symbol and use that name or symbol to look up the associated implementation at run time. Here's how it works:

  1. A client sends a message to a target object.
  2. The message has a name and zero or more arguments.
  3. The target (which can be a class or object) looks to see if it has a method of the same name as the message.
  4. If so, the target object invokes that method.
  5. If not, the target object sends a message to the parent. The parent might be a superclass (Smalltalk), a parent object (Self), or a module (Ruby).
  6. If the method is not found in any parent, an error-trapping method is invoked.

All of the above steps occur at run time. That means that neither the target method nor the implementation needs to exist until the statement with the message is executed. In Smalltalk, everything is an object, and most behaviors make use of message passing. Even control structures rely on it. Smalltalk has three kinds of messages: unary (with no parameters), binary (with a fixed set of parameters), and keyword (with named parameters). For example:

  • 7 sin sends the unary message sin to the target object 7.
  • 3 + 4 is a binary message. It sends the message + to the object 3 with the argument 4.
  • array at: 1 put "value". is a keyword message. The code sends the message at: put: to the array object, placing value at position 1 of the array.
  • condition ifTrue: [doSomething] ifFalse: [doSomethingElse]. is a keyword message. The code between brackets is called a closure, or code block. This code example sends the :ifTrue :ifFalse message to the condition object. If the condition is true, Smalltalk executes the [doSomething] code block; otherwise, it executes the [doSomethingElse] code block.

Ruby supports message passing and direct method invocations. Message passing in Ruby looks slightly different, but the premise is the same. In Listing 2, I define a Dog class, with a speak method and a sleep method. I invoke the speak method directly and invoke sleep by name via the send method.


Listing 2. Invoking a method in two ways with Ruby

irb(main):001:0> class Dog
irb(main):002:1>   def speak
irb(main):003:2>     puts "Arf"
irb(main):004:2>   end
irb(main):005:1>   def sleep
irb(main):006:2>     puts "Zzz"
irb(main):007:2>   end
irb(main):008:1> end
=> nil
irb(main):009:0> dog = Dog.new
=> #<Dog:0x34fa9c>
irb(main):010:0> dog.speak
Arf
=> nil
irb(main):011:0> message = "sleep"
=> "sleep"
irb(main):012:0> dog.send message
Zzz

If you're a Java programmer, I've shown you nothing new. You could certainly handle late binding through Java's reflection API. You'd just need to work much harder to do an equivalent job. But invoking a method with an arbitrary string -- one that you can modify programmatically -- opens up additional capabilities in a language. Most important, you can easily invoke any arbitrary behavior, one that has been added after run time.

The Self language takes this message-passing concept to the extreme. Everything in Self is an object. You invoke all Self behavior exclusively through message passing. Self has no classes (you create new objects by cloning other objects), and no variables (only named slots with methods and objects). Self uses message passing to invoke named slots and methods. Most other object-oriented languages dilute the value of encapsulation by allowing direct access to instance data, but Self overcomes this deficiency by enforcing the same protocol for accessing methods and instance data. Sending a message invokes a slot. If a slot has an object, the object returns its value. Self makes no distinction between accessing attributes and accessing methods. This simplification leads to a simple, powerful programming language. Like Smalltalk, Self represents control structures with message passing, and Self relies on an image that is always running. Objects in Self have a parent object and slots that contain other objects or methods. The heavy reliance on message passing and the notion that Self applications are always running should tip you off that late binding is a central theme in Self.

Message passing in Self works much as it does in Smalltalk. count <- 3 in Smalltalk assigns a number 3 to a variable called count. But Self has no variables and no assignment. You'd send the message object count: 3 to set the count slot on object to a value of 3. To retrieve the value of count, you'd simply use object count.

Message passing takes on another dimension when you throw method_missing into the mix. Remember, this capability is open to dynamic languages, but completely closed to languages that bind at compile time to a type. The benefit of the early binding -- enforcing that the method must exist -- also turns out to be a core weakness. Ruby lets you override the method_missing behavior to invoke behaviors for methods that might not exist at run time. Active Record associates a class with a table and dynamically adds an attribute to each class for every column in the database. Active Record also automatically adds finders for each column, or combination of columns! For example, for a Person class mapped to a people database table having first_name and last_name columns, person.find_by_first_name_and_last_name is a legal Active Record statement, though no such method exists. Active Record simply overrides method_missing and parses the method name at run time to determine whether the method name is valid. The result is an extremely productive framework for wrapping database tables -- one greatly simplified through the power of late binding. But so far, I've only explored the invocation side. It's time to push forward into adding behavior.


Adding behavior at run time

All three languages -- Self, Smalltalk, and Ruby -- make it easy to add behavior at run time. With Self and Smalltalk, any change you make to the existing class is by definition a run-time modification. When you add a method, you're effectively modifying a live class. Adding or deleting a slot is easy in Self: you just send the _add_slot message. Similarly, in Smalltalk, you can add a method or attribute through calling the appropriate message (compile in some dialects). In both cases, you modify a single copy of the classes in the image directly. I'll dive a little deeper into adding behaviors to a class in Ruby.

Ruby frameworks often use techniques that modify classes at run time through several different mechanisms. The easiest is the open class. You can open any Ruby class and change it by renaming, adding, or deleting methods or attributes. Say you wanted to extend numbers in Ruby to make it easy to implement simple bar graphs. You could easily just open up the Fixnum class and add a method to print a bar of the appropriate length, as in Listing 3:


Listing 3. Extending Fixnum

irb(main):001:0> 7.class
=> Fixnum
irb(main):002:0> class Fixnum
irb(main):003:1>   def bar
irb(main):004:2>     puts('-'*self)
irb(main):005:2>   end
irb(main):006:1> end
=> nil
irb(main):007:0> 7
=> 7
irb(main):008:0> 7.bar
-------
=> nil
irb(main):009:0> [6, 8, 2, 4, 9].each {|i| i.bar}
------
--------
--
----
---------
=> [6, 8, 2, 4, 9]


This example shows how to extend any Ruby class when you know the behavior at the time that you run the program. When you want to add arbitrary behavior to a class, based on unknown criteria, you need to use a different technique. You can evaluate a string in the context of a class. Take Listing 4, for example. Listing 3 extended the Fixnum class, which was known statically. In Listing 4, the class need not be known statically at all. This extension of Dog lets you add an arbitrary behavior to the dog, or any other class. The class opens itself, adds in a method of a name you specify, and adds the behaviors that you specify to the class:


Listing 4. An extensible class

class Dog 
  def self.extend(method_name, method_body)
    class_eval(%Q[
      def #{method_name}
        #{method_body}
      end
      
    ])
  end
  
  def speak
    puts "Arf"
  end
  
end

dog = Dog.new
Dog.extend("sneeze", "puts 'Achoo!'")
dog.speak
dog.sneeze

The extend method in Listing 4 needs a little more explanation. Here's the big picture. Ruby opens a class and adds a method with the name and body you provide to the Dog class. First, I define a method called self.extend. The self. means extend is a class method, meaning one method operates on the whole class, like the new method. Next, I call class_eval. This method opens the class and executes the following string on the open class. Next, all code between %[ and ] is interpreted as a single string. Finally, Ruby substitutes the value for the variables between #{ and }.


Discovery and beyond

In the example in Listing 4, I extended Dog from the inside. Using the same techniques, you can extend any arbitrary Ruby class in the same way. Now, the full power of delayed binding should be coming into focus. You can extend an ordinary class with arbitrary capabilities and invoke the new behaviors on a class, though they didn't exist when you wrote the original class.

One final capability completes the overview: reflection. Self, Ruby, and Smalltalk all make reflection trivial. Their message-passing capabilities allow method invocation without forcing users to access a physical method, as they do in the Java language. class.methods gives you an array of method names in Ruby, and class methods returns a dictionary of methods in Smalltalk. Using these capabilities and similar features, you can find just about everything you need for quickly introspecting on a class or object.

So far, I've talked primarily about binding to methods, but delayed binding goes much further. Consider the Ruby method definition shown in Listing 5:


Listing 5. Adding two numbers

def add(x, y)
  x+y
end

With a similar Java method, the parameters would be typed. This Ruby method simply returns the sum of two numbers. The only requirement for using the method is that the first object implements a + and the second object is compatible with the first. The client of the method can determine whether the objects are compatible or not.

A similar Java method would be useful only for a single class of argument. This Ruby method can serve floats, integers, strings, and anything else that supports a + method. Delayed binding makes it possible for a single method to be truly polymorphic. The design is also extensible because it can serve types not yet implemented by the current system. For example, this method could easily support imaginary numbers.


Delaying binding with the Java language

The Java community's obsession with static type checking is curious because Java developers are now spending an ever-increasing amount of energy looking for ways to delay binding. Sometimes, the approaches are successful. Frameworks such as Spring exist primarily to delay binding, which loosens the coupling between client and service. Aspect-oriented programming allows delayed binding by providing services that extend a class beyond its current capabilities. Frameworks like Hibernate delay binding, adding persistence capabilities to plain, ordinary Java objects (POJOs) at run time through reflection, proxies, and other tools. Popular books teach developers how to program with POJOs, often moving beyond reflection with increasingly complex techniques essentially used to open up a class and delay binding, effectively sidestepping static typing.

In other places, the approaches to delayed binding have been less successful. Deployment descriptors, which rely on XML to delay binding, have been much more problematic. The over-reliance on XML is partially driven by our intense desire to have more dynamic behavior in a language that's often a little too static, binds a little too early, and is a little too restrictive.

Languages and techniques exist that offer solutions -- transparent persistence, reduced coupling for testability, richer plug-in models, and many others -- to the types of problems Java developers desperately want to solve. One look at the metaprogramming techniques that power a Java persistence framework, compared to similar solutions in Active Record, Gemstone, or Og (persistence frameworks in dynamic languages) should tell you all you need to know (see Resources). Delayed binding is becoming increasingly important, and the ideas that fuel the process are most productive in other languages. When you need to do metaprogramming, open up your toolbox and throw in some languages that allow delayed binding. Don't be afraid to keep crossing borders.


Resources

Learn

  • Java To Ruby: Things Your Manager Should Know (Tate, Pragmatic Bookshelf, 2006): The author's book about when and where it makes sense to make a switch from Java programming to Ruby on Rails, and how to make it.

  • Beyond Java (Tate, O'Reilly, 2005): The author's book about the Java language's rise and plateau and the technologies that could challenge the Java platform in some niches.

  • Og: An object-relational mapper in Ruby. It hasn't caught on because of Active Record's popularity, but the usage model is compelling.

  • Gemstone: The popular Smalltalk database.

  • Smalltalk.org: A community site for Smalltalk.

  • Rails Up and Running (Tate, O'Reilly, 2006): The author's book about getting started with Ruby on Rails.

  • "Book review: Agile Web Development with Rails" (Darren Torpey, developerWorks, May 2005): Get the scoop on a book that deepens readers' understanding of Rails and the rationale behind agile development approaches.

  • The Spring Framework: Spring is an excellent framework for overcoming the limitations of static typing in the Java language.

  • Programming Ruby (Dave Thomas et al., Pragmatic Bookshelf, 2005): A popular book on Ruby programming.

  • The Rails API: The Rails Framework documentation provides an excellent resource for learning Active Record and the techniques that enable it.

  • The Java technology zone: Hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Squeak: A Smalltalk implementation developed by Disney.

  • Self: Download the Self programming language, a simplified derivative of Smalltalk.

  • Ruby on Rails: Download the open source Ruby on Rails Web framework.

  • Ruby: Get Ruby from the project Web site.

Discuss

About the author

Bruce Tate

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's a Java Champion and the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released From Java to Ruby and Rails: Up and Running. He spent 13 years at IBM and is now the founder of the RapidRed consultancy, where he specializes in lightweight development strategies and architectures based on Ruby and the Ruby on Rails framework. His practice now offers a full range of Ruby and Rails development, education, consulting, and mentoring offerings.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=172780
ArticleTitle=Crossing borders: Delayed binding
publish-date=11072006
author1-email=bruce.tate@j2life.com
author1-email-cc=bruce.tate@j2life.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers