Java.next: Extension without inheritance, Part 1

See how Groovy, Scala, and Clojure bolt behavior onto classes

Groovy, Scala, and Clojure offer many extension mechanisms, whereas inheritance is virtually the Java™ language's only option. This installment looks at category classes, the ExpandoMetaClass, implicit casts, and protocols as ways to extend Java classes with the Java.next languages.

Neal Ford, Director / Software Architect / Meme Wrangler, ThoughtWorks Inc.

Neal FordNeal Ford is Director, Software Architect, and Meme Wrangler at ThoughtWorks, a global IT consultancy. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, and video/DVD presentations, and he is the author or editor of books spanning a variety of technologies, including the most recent Presentation Patterns. He focuses on designing and building large-scale enterprise applications. He is also an internationally acclaimed speaker at developer conferences worldwide. Check out his website.



12 June 2013

Also available in Chinese Russian Japanese

About this series

The Java legacy will be the platform, not the language. More than 200 languages run on the JVM, and it's inevitable that one of them will eventually supplant the Java language as the best way to program the JVM. This series explores three next-generation JVM languages: Groovy, Scala, and Clojure, comparing and contrasting new capabilities and paradigms, to provide Java developers a glimpse into their own near future.

The Java language's design included some purposeful omissions to avoid perceived problems with its predecessors. For example, the designers of the Java language felt that multiple inheritance in C++ engenders too much complexity, so they opted not to include that feature. In fact, they built few extensibility options into the language, relying on single inheritance and interfaces.

Other languages, including the Java.next languages, include a rich ecosystem of extension possibilities. In this installment and the next two, I explore ways to extend Java classes that don't involve inheritance. In this article, you see how to add methods to existing classes — either directly or through syntactic sugar.

The Expression Problem

The Expression Problem is a well-known observation from the recent history of computer science that originated in an unpublished paper by Philip Wadler of Bell Labs (see Resources). (Stuart Sierra does a terrific job of explaining it in detail in his developerWorks article "Solving the Expression Problem with Clojure 1.2.") In his paper, Wadler says:

The Expression Problem is a new name for an old problem. The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts).

In other words, how do you add functionality to classes within a hierarchy without resorting to type casts or if statements?

A simple example shows how the Expression Problem might manifest in the real world. Suppose that your company has always assumed that units of length in applications are in meters, building no capability into your classes for any other denomination. Yet, one day, your company merges with a rival that has always assumed that lengths are in feet.

One way to solve this problem is to make it trivial to switch between the two formats by augmenting Integer with conversion methods. Modern languages offer several solutions that make this possible; in this installment, I focus on three:

  • Open classes
  • Wrapper classes
  • Protocols

Groovy's categories and ExpandoMetaClass

Groovy includes two distinct ways to extend existing classes by using open classes— the ability to "reopen" a class definition to make changes (for example, to add, change, or remove methods).

Category classes

Category classes, a concept that is borrowed from Objective-C, are regular classes with static methods. Each method accepts at least one parameter, which represents the type that the method augments. If I want to add methods to Integer, for example, I need static methods that accept that type as the first parameter, as in Listing 1:

Listing 1. Groovy's category classes
class IntegerConv {
  static Double getAsMeters(Integer self) {
    self * 0.30480
  }

  static Double getAsFeet(Integer self) {
    self * 3.2808
  }
}

The IntegerConv class in Listing 1 contains two augmentation methods, each accepting an Integer parameter named self (a common conventional name). To use these methods, I must wrap the referencing code in a use block, as in Listing 2:

Listing 2. Using category classes
@Test void test_conversion_with_category() {
  use(IntegerConv) {
    assertEquals(1 * 3.2808, 1.asFeet, 0.1)
    assertEquals(1 * 0.30480, 1.asMeters, 0.1)
  }
}

Two things of particular interest appear in Listing 2. First, even though the extension method in Listing 1 is named getAsMeters(), I call it as 1.asMeters. Groovy's syntactic sugar around properties in Java allows me to execute the getAsMeters() method as if it were a field of the class named asMeters. If I omit the as in the extension methods, the calls to the extension methods require empty parenthesis, as in 1.asMeters(). Generally, I prefer the cleaner property syntax, which is a common technique for writing domain-specific languages (DSLs).

The second thing of note in Listing 2 are the calls to asFeet and asMeters. Within the use block, I call the new methods identically to built-in methods. The extension is transparent within the lexical scope of the use block, which is nice because it limits the scope of the augmentation to (sometimes core) classes.

ExpandoMetaClass

Categories were the first extension mechanism that Groovy added, but its lexical scoping proved to be too limiting for building Grails, the web framework that is based on Groovy. Frustrated with the limitations in categories, Graeme Rocher, one of the Grails creators, added another extension mechanism to Groovy: the ExpandoMetaClass.

ExpandoMetaClass is a lazily instantiated extension holder that can "grow" from any class. Listing 3 shows how to implement my extension to the Integer class by using ExpandoMetaClass:

Listing 3. Using ExpandoMetaClass to extend Integer
class IntegerConvTest{

  static {
    Integer.metaClass.getAsM { ->
      delegate * 0.30480
    }

    Integer.metaClass.getAsFt { ->
      delegate * 3.2808
    }
  }

  @Test void conversion_with_expando() {
    assertTrue 1.asM == 0.30480
    assertTrue 1.asFt == 3.2808
  }
}

In Listing 3, I use the metaClass holder to add asM and asFt properties, with the same naming convention as Listing 2. The call to the metaclass appears in a static initializer for the test class because I must ensure that the augmentation occurs before the extension method is encountered.

Both category classes and the ExpandoMetaClass call the extension-class methods before built-in methods. This enables you to add, change, or remove existing methods. Listing 4 shows an example:

Listing 4. Extension classes that supersede existing methods
@Test void expando_order() {
  try {
    1.decode()
  } catch(NullPointerException ex) {
    println("can't decode with no parameters")
  }
  Integer.metaClass.decode { ->
    delegate * Math.PI;
  }
  assertEquals(1.decode(), Math.PI, 0.1)
}

The first decode() method invocation in Listing 4 is a built-in static Groovy method that is designed to change integer encodings. It normally accepts a single parameter; when called with no parameter, it throws a NullPointerException. Yet, when I augment the Integer class with my own decode() method, it supersedes the original.


Scala's implicit casts

Scala attacks this aspect of the Expression Problem by using wrapper classes. To add a method to a class, you add it to a helper class and then provide an implicit cast from the original class to your helper. Once the cast occurs, you're invisibly calling the method from the helper rather than the original class. The example in Listing 5 uses this technique:

Listing 5. Scala's implicit casts
class UnitWrapper(i: Int) {
  def asFt = {
    i * 3.2808
  }

  def asM = {
    i * 0.30480
  }
}

implicit def unitWrapper(i:Int) = new UnitWrapper(i)

println("1 foot = " + 1.asM + " meters");
println("1 meter = " + 1.asFt + "foot")

In Listing 5, I define a helper class named UnitWrapper that accepts a single constructor parameter and two methods: asFt and asM. After I have the helper class that converts values, I create an implicit def, instantiating a new UnitWrapper. To call the method, I simply call it as if it were a method of the original class, such as 1.asM. When Scala fails to find the asM method on the Integer class, it checks for the presence of implicit conversions that allow it to convert the calling class into one with the target methods. Like Groovy, Scala has syntactic sugar that enables me to omit the parentheses for the method calls — but as a language feature rather than a naming convention.

Conversion helpers in Scala are usually objects rather than classes, but I use a class because I want to pass the value as a constructor parameter (which isn't allowed for objects).

Implicit casts in Scala are an elegant, type-safe way to augment existing classes, but you can't change or remove existing methods with this mechanism as you can with open classes.


Clojure's protocols

Clojure takes yet another approach to this aspect of the Expression Problem, by using a combination of the extend function and the Clojure protocol abstraction. A protocol is conceptually like a Java interface: a collection of method signatures with no implementation. Even though Clojure isn't object-oriented but rather functional in nature, you can interact with (and extend) classes and map methods to functions.

To extend numbers to add conversions, I define a protocol that contains my two functions (asF and asM). I can use the protocol to extend an existing class (such as Number). The extend function accepts the target class as the first parameter, the protocol as the second, and a map with function names as keys and implementations (as anonymous functions) as the values. Listing 6 shows the Clojure unit conversion:

Listing 6. Clojure's protocols for extension
(defprotocol UnitConversions
  (asF [this])
  (asM [this]))

(extend Number
  UnitConversions
  {:asF (fn [this] (* this 3.2808))
   :asM #(* % 0.30480)})

I can use the new extension at the Clojure REPL (interactive read-eval-print loop) to verify the conversion:

user=> (println "1 foot is " (asM 1) " meters")
1 foot is  0.3048  meters

In Listing 6, the implementation of the two conversion functions illustrates two syntax variants for anonymous-function declarations. Each function accepts a single parameter (this in the asF function). Single-argument functions are so common that Clojure has syntactic sugar for their creation, as illustrated by the AsM function, where % is the parameter placeholder.

Protocols create a simple solution to adding methods (as functions) to existing classes. Clojure also includes some useful macros that enable you to consolidate of a group of extensions. For example, the Compojure web framework (see Resources) uses protocols to extend various types so that they "know" how to render themselves. Listing 7 shows a snippet from the Renderable definition in Compojure:

Listing 7. Extending many types through protocols
(defprotocol Renderable
  (render [this request]
    "Render the object into a form suitable for the given request map."))

(extend-protocol Renderable
  nil
  (render [_ _] nil)
  String
  (render [body _]
    (-> (response body)
        (content-type "text/html; charset=utf-8")))
  APersistentMap
  (render [resp-map _]
    (merge (with-meta (response "") (meta resp-map))
           resp-map))
  IFn
  (render [func request]
    (render (func request) 
  ; . . .

In Listing 7, the Renderable protocol is defined with a single render function that accepts a value and a request map as parameters. Clojure's extend-protocol macro, which lets you group protocol definitions together, accepts pairs of types and implementations. In Clojure, you can substitute an underscore for parameters you don't care about. In Listing 7, the visible part of this definition provides rendering instructions to nil, String, APersistentMap, and IFn (the core interface for functions in Clojure). (Many more types are included in the framework but omitted from the listing to save space.) Notice how nicely this works in practice: For all the types that you might need to render, you can define the semantics and extensions together.


Conclusion

In this installment, I introduced the Expression Problem and delved into details of how the Java.next languages handle one aspect: the clean extension of existing classes. Each of the languages uses a different technique (Groovy uses open classes, Scala uses wrapper classes, and Clojure implements protocols), with similar results.

However, the Expression Problem goes deeper than type augmentation. In the next installment, I continue to discuss extension with other protocol capabilities, traits, and mix-ins.

Resources

Learn

Get products and technologies

  • Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users as you explore the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=933633
ArticleTitle=Java.next: Extension without inheritance, Part 1
publish-date=06122013