Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Friday, February 11, 2005

Tapestry and Ruby On Rails at Utah JUG

Next week (Feb 17 2005), I'll be presenting a bit on Tapestry to the Utah Java User's Group. Jamis Buck will also be there, discussing Ruby on Rails. Jamis has done some very interesting work in the Ruby space, some of it based on HiveMind, and it will be interesting to meet him.

HiveMind and Pico Compared on java.net

Just read IoC Container Face-Off by Ken Ramirez, an article introducing the concepts of Inversion of Control and two implementations: HiveMind and PicoContainer. Curiously, Spring is not even mentioned!

I had a few minor issues with the article; I don't think it made the case very well for IoC. It confused "dependency injection" with IoC (dependency injection is an important component of IoC, but inversion of control also includes life cycle issues well beyond connecting collaborating services).

Where the article was weakest was on that exact topic: collaborating services. If you have just a main() method call a single service, IoC looks like a waste of effort. It's when you build complex systems with many moving parts ... but those individual parts are simple and testable, that IoC approach really shines.

The code to load a descriptor is too complicated; just put the hivemodule.xml on the classpath and use RegistryBuilder.constructDefaultRegistry().

Some of the terminology is about six months out of date; "singleton" and "deferred" became "primitive" and "singleton". Ken also garbled what the LoggingInterceptor does (it logs method entry and exit, not just exceptions) and missed most of the strengths of HiveMind:

  • HiveMind supports a large number of modules, which is why the extended naming is necessary.
  • It is very common to have many services implementing the same interface.
  • Not even a mention of configurations, HiveMind's most obvious differentiator from other containers.
  • No mention of factories ... many of the most interesting services in HiveMind are created dynamically at runtime.

For a supposed "face off", there was no direct comparison of the two frameworks. Here, I'll do it: PicoContainer is lighter weight, has an aversion to XML, doesn't mandate the use of interfaces, and uses constructor injection exclusively. HiveMind is driven by XML, is much heavier weight (what with support for many modules, interceptors, configurations, etc.), enforces the use of interfaces, and allows setter and constructor injection.

HiveMind 1.1 and multiple locales

In HiveMind 1.0, you set the locale once, when you build the Registry and you're stuck with it. That's pretty limiting, especially for a web application. In a web application, such as a Tapestry application, each request (which is to say, each thread) may be using a different locale.

Using a fixed locale limits the usefulness of HiveMind. It means that the business layer can't generate localized error and status messages and pass them back up to the presentation layer. Instead, you fall into the same old trap of exceptions and return codes so that the presentation layer can generate those messages (in the proper locale). That's counter to whole philosophy of HiveMind: colocate everything, so that they can collaborate richly and limit the amount of ugly plumbing and klude code you have to write.

Well, I just checked in changes to HiveMind 1.1 that allow the locale to be changed at any time. The Registry's locale is simply the default locale. The key aspect of this is the Messages object injected into your services ... this was how services gained access to localized messages. The new implementation is smart enough to combine the request message key with the thread's current locale when obtaining a message.

For a web application, this means that, as soon as the client's locale is determined, the ThreadLocale service should be notified. Any messages generated by any services will then be in the proper locale. Tapestry 3.1 has a particular place for doing this ... in fact, the engine's getLocale() and setLocale() methods will be changed to simply invoke getLocale() and setLocale() on the ThreadLocale service.

Thursday, February 10, 2005

Streamlining HiveMind Module Deployment Descriptors

We often complain about the verbosity of J2EE XML descriptors. The HiveMind descriptors are better, but there's still a lot of typing.

One thing that struck me is that there's a lot of needless duplication of package names. Often the package name matches the module id ... and yet, you'll retype the package name for each service interface and each class.

So ... what if HiveMind just assumed that simple interface and class names were in a specific package? Also, what if the module id and the package name aren't the same?

What I've done it to add a package attribute to <module> (which defaults to the module id). I modified a lot of code to make use of the package name when a reference to an object type shows up (and, in fact, improved the code by adding a resolveType() method to the Module interface). Literally, if the class name as is doesn't exist, then a second try (prefixing the class name with the package name) is made.

This simplifies the examples module descriptor:

<?xml version="1.0"?>
<module id="examples" version="1.0.0" package="org.apache.hivemind.examples">
    <service-point id="Adder" interface="Adder">
        <create-instance class="impl.AdderImpl"/>
        <interceptor service-id="hivemind.LoggingInterceptor"/>
    </service-point>
 . . .

What's nice is that you can use "relative" class name, such as impl.AdderImpl, which is really org.apache.hivemind.examples.impl.AdderImpl. See what I mean about less typing?

Perhaps we'll go further in the future ... for instance, maybe <service-point> should assume that the interface matches the service point id?

There will be cases where you really want to support many different packages in the same JAR. You will then either type fully qualified class names, or split your JAR into multiple logical modules using <sub-module>.

Sunday, February 06, 2005

Global message catalog for Tapestry

One of the most frequent requests for Tapestry is support for global message catalogs. In Tapestry, each page and each component can have its own message catalog and that's good ... but this can also foster some duplication between templates. This is now fixed as TAPESTRY-242.

I think of all the changes I've made in Tapestry 3.1 so far, this may be the first one that came as a request from the community, rather than a change I've made from my personal vision for 3.1. Now that the basic infrastructure of 3.1 is in place (but subject to some revision and refactoring) I believe we'll be seeing these kind of changes rolling out quite frequently.

Saturday, February 05, 2005

Tapestry, JSF and FUD

Rick Hightower's recent article, "JSF for nonbelievers: Clearing the FUD about JSF" has been prompting some discussion on TheServerSide. Now, one of the good things about JSF is the way it validates (in some people's eyes) the component based approach.

It's natural for me to be politely antagonistic towards JSF. JSF has an event model and a component model and I find it bothersome for them to claim that JSF has the event model and the component model. Their choices are just their choices.

The one thing to keep in mind during any discussion of these technologies is that, from a high enough level, they are all identical. HTTP requests flow in, HTML (or other) responses flow out. This is the same for CGI, Perl, ASP.NET, etc. The differences are not in what's possible, but in how hard it is to create and how easy it is to maintain. While I'm envious about certain aspects of JSF (particularly enforced acceptance from above, lack of enforced inheritance, and having design-time tool support built in), my basic stance, unchanged from reading this article, is that real-world Tapestry applications are faster to build, easier to maintain and extend, and more robust in deployment.

JSF and JSP technology

I find this aspect of JSF quite interesting; the choice to "support" JSP creates some strange behaviors that would be unacceptable if it wasn't coming out of Sun and the JCP. In Tapestry, when any aspect of a page or component changes (and you've disabled caching for development purposes), then templates and the component tree stay synchronized ... because they are the same thing.

Figure 1. Example application from an MVC point of view

The Tapestry equivalent of this is, I think, a bit simpler. There's a lot less to map because the Tapestry HTML template is tied to the component tree. Tapestry component and page classes are the equivalent of the JSF backing bean (though it is quite practical to have both properties and listener methods in a different object entirely).

faces-config.xml

One of the things that bothers me about JSF is the fact that managed beans are entirely global. Tapestry allows each page or component to manage its own set of beans, and has additional lifecycles (the equivalent of bean scopes).

For me, the navigation rules are problematic. Because of how the view (templates) and component model are so loosely tied together, it becomes necessary to add another level of abstraction, the outcome/view-id mapping. If you corner a JSF developer and challenge them about the need for this, the response will likely be something about supporting different views (i.e., WML, Flash, etc.) from the same set of controls.

And that's a problem for me. I've repeatedly asked my audiences at various events who has done this, or needed to do this ... support multiple view types with a single application. I've yet to find anyone who needs to do this or finds it practical. This was the promise of XML/HTTP pipelines before JSF. The problem is, this is a non-starter of an issue ... there's no such thing as a single application that support multiple views.

What there are is multiple similar applications that share common back end processing and data. When you try and have one application shoe horn into multiple view technologies, you are asking for a disaster ... I call it "coding inside a case statement". You'll be adding, removing, moving and morphing so much stuff that you end up with something that is brittle in all views.

My response when this kind of feature is asked for in Tapestry is to challenge the questioner about what the different forms of the application will look like ... and to point out that they are different applications that should share a common back end. In Tapestry terms, they may even share page classes and components ... but the templates should be unique to each view. The ultimate goal is to keep the templates clean because any other approach is incompatible with enterprise application development realities. Good for demos, bad for real life.

By contrast, Tapestry pages concentrate on just on type of markup at a time, typically HTML (but just as easily XHTML, XML or WML). This allows the kind of fine-grained control that demanding page designers expect.

Case in point is Tapestry's support for informal parameters. Tapestry components may optionally support additional, arbitrary parameters beyond the explicit, named (and typed) set of parameters. The majority do. The parameters (which may be literal values, evaluated expressions, or localized message -- just like formal parameters) are passed through as additional attributes on the element. This allows you to specify any and all HTML attributes; particularly useful for dealing with CSS styles or JavaScript event handlers. Having a tight binding between the template engine and the component model makes this easy ... using JSPs (where each JSP tag attribute must be formally defined) would make this impossible.

A final note; I'm dissapointed that the name of the calculator controller bean, CalcBean, is a "simple" name, not a qualified name. From the later examples, it appears that such simple names are mandated by the expression language. On large projects, this will simply encourage naming conflicts. My experience, as far back as 1997, is that naming conflicts can wreak havoc on large team projects ... and Tapestry is specifically designed so that naming conflicts simply don't occur.

Gluing the model and the view

Tapestry allows you to have as much or as little flexibility as you want in terms of where properties and operations are located. My preference is to co-locate properties and operations since that is the very definition of object-oriented programming.

In Rick's example, he demonstrates how the pure business logic can be externalized, by having a Calculator class that is separate from the CalculatorController that is referenced inside JSF.

Certainly the same thing is possible in Tapestry. The equalivalent Tapestry page class would be:

public abstract class CalculatorPage extends BasePage
{
  public abstract int getFirstNumber();
  public abstract int getSecondNumber();

  private Calculator _calculator = new  Calculator();

  // Listener method for adding.

  public void add(IRequestCycle cycle)
  {
    int result = _calculator.add(getFirstNumber(), getSecondNumber());

    showResult(result);
  }


  public void multiply(IRequestCycle cycle)
  {
    int result = _calculator.multiply(getFirstNumber(), getSecondNumber());

    showResult(result);
  }

  private void showResult(int result)
  {
    IRequestCycle cycle = getRequestCycle();
    ShowResult page = (ShowResult) cycle.getPage("ShowResult");

    page.setFirstNumber(getFirstNumber());
    page.setSecondNumber(getSecondNumber());
    page.setResult(result);

    cycle.activate(page);
  }
}

This demonstrates some advantages of JSF and some advantages of Tapestry. JSF doesn't require you to extend from a base class, which is a good thing (and a huge, backwards incompatible change for Tapestry, which is why it hasn't been implemented yet).

The abstract class, with abstract accessor, causes a bit of confusion; it is because Tapestry injects code into your class by subclassing it and filling in the abstract methods. In this way, it can efficiently manage the properties of your page ... storing persistent properties in the HttpSession as they change, and properly resetting the values for transient and persistent properties at the end of each request. This reflects the Efficiency principle of Tapestry ... the expensive to construct page objects are pooled between requests and shared by different sessions from one request to then next; the enhanced subclass fulfills the contract needed by Tapestry to safely share the page objects in this way.

The method invocation is the same; Tapestry 3.0 requires that such listener methods take a single IRequestCycle parameter (Tapestry 3.1 will likely improve on this). The difference is how that method is reference; in Tapestry, the Submit component can reference the method as:

  <input jwcid="@Submit" listener="ognl:listeners.add" value="Add"/>

In Tapestry syntax; this means "an anonymous instance of the Submit component, with its listener parameter bound to the add method of the page class".

An advantage for Tapestry, I think, is the way the two pages (the firsts containing the form, the second displaying the result) communicate ... in proper, type-safe Java code. The first page obtains an instance of the "ShowResult", casts it down from IPage to its actual type, and invokes methods on it to inform it of what it needs to operate. This is a clean interface between the two pages ... the first page concerned with collecting the two values and calculating a result, the second with displaying the two values and the result. The are many variations on this "Tapestry bucket brigade" that will appear more or less efficient. For example, we could have a data object containing the first and second numbers and the result, and pass that single object between the pages.

Again, this is more of an object-oriented approach. The pages of the application are objects, at least for Tapestry. The proper way for them to communicate is via methods and properties ... rather than the engineered coincidence that they both reference the same, arbitrarily named bean stored as an HttpServletRequest attribute. JSF is much more beholden to the Servlet APIs than Tapestry ... which does much more to hide the APIs and the mechanisms they represent.

A final note on this subject; in Rick's example, the CalculatorController was given session scope; a somewhat odd choice. I suspect the reasoning for this will become evident as the examples expand in later chapters. In any case, the Tapestry example will be request scope and the application will itself be stateless (no HttpSession).

JSP vs. HTML template

In this simple example, JSF has a slight advantage, in that JSF input fields include validation by default. Even so, the JSPs here would not preview correctly inside any HTML editor ... it would require a JSF-aware tool to render a preview properly. By contrast, Tapestry HTML templates are ordinary HTML elements and attributes (with an occasional extra, non-HTML attribute thrown in) and will preview properly in any WYSIWYG HTML editor.

In Tapestry, form input validation is an add-on, requiring a different component, TextField. However, Tapestry's form input validation is quite powerful, with tremendous control over look and feel, error message formatting and reporting, and support for complex forms containing loops and conditionals.

Conclusions

Rick has demonstrated that your can assemble simple JSF applications without tool support. JSF does have an improved model over typical Struts development. I look forward to more articles in this series, as I think it will prove an excellent way of differentiating Tapestry 3.0 from JSF.

Certainly, I've seen nothing in any publication about JSF that would make me consider "closing up shop". At a high level, yes, the seem quite similar ... but there are basic assumptions and pervasive practices in both JSF and Tapestry that result in worlds of differences when you sit down to build a real application.

I expect to take a few minutes to put together Tapestry versions of Rick's examples. Monitor this blog for the details ... and remember that Tapestry discussions are best held on tapestry-user@jakarta.apache.org.

Saturday, January 29, 2005

Should MyEclipse support Tapestry? Using Spindle?

There's a discussion going on in the MyEclipse discussion forums about adding Tapestry support to MyEclipse. I haven't used MyEclipse, but I've heard of it ... it's a collection of Eclipse plugins to support J2EE development and supports a number of standards and tools, such as JSF and Hibernate.

Now, MyEclipse is inexpensive (it's based on an annual subscription, which is a fun idea), but it is proprietary and Spindle is free ... but I can't help thiking that an improved/integrated Spindle as part of MyEclipse would be a good thing, and may help offload some of Geoff's vast effort with maintaining and extending Spindle (especially if some improvements worked backwards into Spindle). I can't wait for Geoff to weigh in on this.

Wednesday, January 26, 2005

Improved HiveDoc

I've spent the last day or so, in between shoveling out my driveway, creating a new HiveDoc XSLT stylesheet with pretty darn good results. I think this is more readable, and better layed out (to support more tweaking of the CSS). Still not as pretty as Spring's equivalent (and is thiers a direct result of ours, or parallel evolution?).

TSS relaunches on Tapestry

Sure, it's old news that TheServerSide has been running on Tapestry for the last several months (I did the work myself). I also wrote an article about it, which is finally available ... at TheServerSide.

Safety First

Friends Don't Let Friends Code Struts

'Nuff said.

Monday, January 24, 2005

Airline Frustration

Sunday 3pm. Nope (2 hours on hold) -- Monday 6:40 am. Ok, REALLY, (40 minutes on hold) Monday 3:00 pm and you connect through Pittsburgh. No jokes, 3:30 pm and you WILL make your connection. 65 minutes in line ... oh, you won't make your connection.And you skycapped your luggage ... very silly. Gee, that's odd, the computer can't locate your baggage information, but that happens sometimes ... I'm sure it will turn up.

So Suzy had to come pick me back up at the airport, and 93S to Quincy was jammed solid so we (2 hours) detoured through Jamacia Plain and Dorcester.

The theme of this blog was a trip to San Francisco to give some Tapestry training. Alas, we'll just have to do it next week instead.

Wednesday, January 19, 2005

HiveMind 1.1-alpha-1 Released

An early preview of HiveMind 1.1, 1.1-alpha-1 has been released on Jakarta.

I think the code and functionality in this release is stable and well tested, but the HiveMind crew has a lot more that will go into the final 1.1. release!

Sunday, January 16, 2005

HiveMind In Chains

The Gang of Four's Chain of Command pattern is a very useful one; we all use it all the time whether we realize it or not. David Geary has just started using it in anger with JSF, starting with the Jakarta commons-chain framework.

Chain of Command is very simple; you have a list of objects that all implement a particular interface. You simply invoke the same method on each object (in a specific order), until one object indicates that the chain is complete. Typically, the methods return a boolean, and a return value of true indicates that the chain has completed.

HiveMind 1.1 already includes an implementation of the Adapter pattern, so it was time to create Chain of Command. This takes the form of a general purpose ChainBuilder, service and a ChainFactory service used to build service implementations.

What differentiates HiveMind's Chain of Command from other implementations is that command interface is arbitrary and application-specification. There's no Command interface; the implementation generated on-the-fly adapts to whatever interface you already have. Your interface may have any number of methods, with any return type (including void), parameters, and exceptions. In 132 lines of (non-comment) code.

Using the ChainFactory, the chain of command becomes just another service implementing the interface, ready to be injected into any other service that needs it.

Tapestry 3.1 already has a number of command chains; it will be nice to refactor that code around this new functionality.

I think a good number of developers out there are aware of many of the Gang of Four's design patterns ... but they have trouble seeing how those patterns fit into their own applications. In the Gang of Four designs, the necessary objects are always instantiated and connected to each other, but it's left as a puzzle to the reader to deduce how they got that way.

In my terminology, the Gang of Four patterns focus on the production state of the system and of the individual services, and ignore the construction state, just as they ignore the destruction state. To be honest, I don't have a copy of the book handy, and I'm sure some of the examples do get involved in this issue (and some of the patterns are themselves creational).

Regardless, HiveMind takes this bull by the horns, because it's largely about the construction state. Using a combination of a HiveMind configuration and a service implementation factory, it becomes quite natural to have a configuration that describes those production state relationships, and create a service that encapsulates the behavior with those relationship in place. For chains, the configuration point describes the commands and their order, and the service implementation factory builds a service implementation that invokes the methods on the commands for you.

Tuesday, January 11, 2005

Monday, January 10, 2005

NFJS Schedule for 2005

Jay and I worked out my appearanced at the No Fluff Just Stuff symposiums series for the first half of 2005. I'm trying to keep it down to two per month, and it's looking good.

  • Atlantic Northeast Software Symposium
    Philadelphia, PA
    March 11-13, 2005
  • Gateway Software Symposium
    St. Louis, MO
    March 18-20, 2005
  • New England Software Symposium
    Boston, MA
    April 8-10, 2005
  • win Cities Software Symposium
    Minneapolis, MN
    April 29-May 1, 2005
  • Rocky Mountain Software Symposium
    Denver, CO
    May 13-15, 2005
  • Central Florida Software Symposium
    Orlando, FL
    June 24-26, 2005

I'm doing my three standard sessions(Tapestry, Tapestry Components and HiveMind) and am working on additional sessions... one of which is about bytecode enhancements for testing (EasyMock) and at runtime (Javassist and HiveMind). Once Tapestry 3.1 is in beta, I may add a session on improvements in 3.1.

I hope to have some more "real world" examples ready shortly ... and, I can't wait to simplify the presentations with the 3.1 improvements. So much less handwaving will be needed! I won't have to talk about parameter directions anymore!

Saturday, January 08, 2005

Friday, January 07, 2005

Seperation of Concerns vs. Inheritance

One of my coding catch-phrases is Aggregation Trumps Inheritance. By that, I mean that combining small simple objects is a more powerful technique than inheritance.

I didn't always think this; coming out of the Objective-C/NextStep camp, I was used to using lots of inheritance. In fact, for a long time, I thought a framework was a set of base classes for me to subclass. You can see this in the implementation of Tapestry, where you start with Tapestry base classes.

Even at the time, I was concerned that the Three Amigo's had a problem with UML. Namely, that when doing a sequence diagram, it was very, very awkward to show the flow from an object to a super-class implementation of a method. There simply wasn't the necessary geometric direction to draw the line. This seemed to be a problem ... UML didn't seem to handle inheritance very well, and that made it difficult to diagram some design I had ... especially those that involved overriding a base class implementation of a method.

In retrospect, the idea of a framework as a set base classes is a bit flawed. It made sense in Objective-C land, due to the lack of a garbage collector. Memory management was a buggy, agonizing process (remember retain cycles, anyone?) so you wanted to minimize the number of objects allocated. Therefore, better to subclass and allocate a single object than to allocate several related objects and have to manage who-owns-who.

That isn't the approach I take any longer; if you look at Tapestry or HiveMind, you'll see how I trust the garbage collector, and use large numbers of really small objects ... objects that may implement an interface or two, but are otherwise inheritance unencumbered, you know, POJOs (plain old Java objects). And, if I'm inclined to diagram in UML, it works fine ... no ambiguity about which object owns which implementation of which method. HiveMind especially has very few base classes or interfaces exposed to your code, which is the way it should be.

As I take Separation of Concerns ever more seriously, I see more places where I was using inheritance out of inertia or reflex, and I can code better using smaller objects. Most often, I'm using the GoF Strategy pattern.

For example, I was adding a simple expression parser to HiveMind. I started thinking about a PropertyToken and a ClassNameToken as the leaves of my AST (Abstract Syntax Tree), with AndToken, OrToken and NotToken classes to add structure. Each node would have an evaluate() method that would return true or false. The leaf tokens would do some real work (see if a JVM System Property is true, or see if a class exists) and the other tokens would combine those values together. Real CompSci Parser 101 stuff.

But then I noticed that I really had two concerns here: the structure of the AST, and the way each node is evaluated. Using inheritance, I would inherit the AST structural behavior from some AbstractToken base class, and the subclasses would each provide their own evaluate() method implementation (as well as any additional properties).

One I saw it that way, I realized that the evaluation part was completely separate and could be factored out. My final solution for the AST uses a Node class, and an Evaluator interface. Each Node owns a left and right child node, and an evaluator. The Node class is about the structure of the AST ... the evaluator is about how the Node evaluates to true or false. The Node.evaluate() method internally delegates to the Node's evaluator (the Node passes itself as a parameter).

The end result was much less code to write and test. First I tested the Node class to make sure that structure and evaluation worked correctly. Then I defined Evaluator implementations (AndEvaluator, OrEvaluator, etc.). Ultimately, And, Or and Not were completely stateless internally ... so I made them singletons. Breaking the code apart this way made it easier to mock Evaluators when I was testing Nodes and vice-versa.

I'm tackling a similar problem in Tapestry now: re-worked the way page recorders work. In Tapestry 3.0, a page recorder is responsible for persisting certain page properties into the HttpSession as attributes, and restoring page properties from those attributes in later requests.

The 3.0 code is broken in a couple of ways; the page recorders are owned by the engine, not the request cycle, which can cause conflicts when you build a Tapestry application using frames (updating the frames cause race conditions as different threads use and update the page recorders in different ways).

It was always my intention to allow different implementations of IPageRecorder, so that other schemes could be used, such as storing data in HTTP Cookies ... but that never happened.

With HiveMind providing configuration and infrastructure, it will be much more reasonable to make this pluggable. In the long term, I want to support more complex life cycles for page data ... such as properties that stay persistent until you navigate to some other page in the application.

So, I'm finding that in the new code, the page recorder is a thin buffer between the page instance and a PropertyPersistenceStrategy object that does the actual work ... and the strategy object is dynamically looked up (using a name stored in the page specification), which is the key to pluggability.

The page recorders can now be lightweight, created as part of a request and discarded afterwards; goodbye thread contention and it simplifies the life cycle and the IPageRecorder interface.

Anyway, back to the moral: if you can subdivide an object into smaller pieces ... do it! Any time you can change an "is-a" relationship to a "has-a" relationship, you are going to find advantages in coding, testing, the works!

Pragmatic vs. Dogmatic Languages

So, my earlier post about Ruby has sparked some comments by Glenn Vanderburg. Glenn was standing next to Dave Thomas during that "hard sell" of Ruby, then proceeded to demo Seaside (a continuations-based Smalltalk web framework) during the drive back to the airport (I only got a small peek because I was driving; Erik Hatcher was the main audience).

Glenn appreciated the fact that the update-copyrights.rb script I wrote, shabby as it is, is a Ruby script, and not Java code in Ruby syntax. In other words, I did my best to use Ruby-isms, including a little duck typing, blocks and iterators, and so forth. We're both disappointed I didn't write tests for the script.

This gave rise to some interesting email discussions, the most productive part of which was some new terminology (that I hope I did not subconsciously steal from someone):

  • Pragmatic vs. Dogmatic Everyone gets hung up on types, dynamic vs. static, scripting vs. everything-else. I'd rather use "pragmatic" for non-type-encumbered languages (Ruby, Perl, Python, JavaScript) and "dogmatic" for Java, C#, COBOL ... everything that forces structure down your throat.

    In Java 1.5 terms, I like auto-boxing, but generic types are abominable! It makes the code much harder to read and the only gain is a minimal amount of compile-time type safety -- that isn't really safe. Dogma. Apparently, the dogma at Sun is "Java code that performs no casts must not ever throw a ClassCastException". So much for Dave Thomas' (and others') idea that the Java compiler should just quietly inject the necessary cast for you (based on all the other type information available) ... instead we have the syntax from hell.

  • lovely spareness is what I like in Ruby, maybe Python ... what I miss from Objective-C. You get a lot done in very little code and the code is readable. It's the aesthetic of Ruby and it's admirable.

These terms, and the concepts behind them, resonate with me. Looking at how both Tapestry and HiveMind have been evolving, it is much the same: less dogma (well, beyond "services must have interfaces!"), and much more spare XML, with the frameworks doing a better job figuring things out from defaults. Ruby is full of this same "less is more" philosophy, which is why people get passionate about it.

My last big change of language was from Objective-C on NextStep. The transition from Objective-C to Java was initially very painful ... so much of Java seemed unnecessarily baroque. And a couple of key features I pined for were missing. In fact, those features have only shown up more recently as Java Aspects ... and as Ruby Mixins. Aspects make my eyes hurt; Mixins are minimal, and powerful. Ruby is something like coming home again to Objective-C.

Ruby won't be my primary tool for a while (if ever), but it's definitely something I want to keep in my toolbox, ready to go.

Wednesday, January 05, 2005

Playing with Ruby

So, I've been peeking a bit at Ruby in tiny fractions of spare time over the last few weeks. Ruby is a fully object-oriented, dynamically typed, scripting language. It's designed to be fast, simple and powerful. It has closures which are just amazing (if difficult to describe), and the lack of types makes many common programming patterns much less of a chore.

I first heard about it a year or two ago from Greg Burd (he's the kind of guy who just knows what's cool before other people do).

I didn't really give Ruby another look until recently. Dave Thomas really pushes Ruby on a (somewhat confused) Java audience at the NoFluffJustStuff symposiums. I too was resistant, but Dave really does a hard sell on Ruby ... how could I resist something that's "a dog's breakfast, but it works!". Dave sealed the deal by sending me a copy of his book (Programming Ruby, aka "The PickAxe", which suffered an untimely death-by-NyQuil on the way out to ApacheCon and had to be replaced).

In fact, with the rolling over of the new year, I found a good use for Ruby ... a copyright updater for the Tapestry and HiveMind source code. I had written one in Python a ways back, but it was limited to Java files.

What I wanted was something a bit smarter ... that would be able to adapt to different types of files (Java, XML, properties) and would be able to update the copyright message, rather than overriding it. That is, convert:

# Copyright 2004 The Apache Software Foundation
To:
# Copyright 2004, 2005 The Apache Software Foundation

My trembling first journey into Ruby is this script, which does the work. It's sloppy, doesn't report errors well, and took me too long to write (almost as long as it would have in Java!) ... but it works and is impressively fast. In fact, I've been very surprised at just how fast Ruby is to load, parse and execute. Visibly faster than Python ... faster than Java I'd bet.

I may have to revise some of my comments in my upcoming TheServerSide Tech Talk (filmed last April).

Dr. Dobb's reviews Tapestry In Action

Just came across this brief review of Tapestry In Action. They generally liked it, but criticized the index (a not unheard of complaint). The index was the most painful part of a very painful process, and it shows.