Language for Simplified Web Services and XML Programming
Comparisons
Water and Java Examples

By Christopher Fry

Core Language Flexibilities

Java is half object-oriented
Vector v = new Vector();
v.addElement(new Integer(37));         ((Integer)(v.elementAt(0))).intValue();

Water is object-oriented
<set v=<thing/> />
v.<insert 37/>
v.0

Object system completeness

Java needs a special syntax for creating objects, i.e.“new Vector()”.
Water uses the same syntax for creating new objects as it does for calling methods.
Java has a “Vector” type.
Water can treat any old object as a vector and so the “vector” type isn’t necessary, though Water does have a vector type when you want to limit the keys of your objects to be consecutive non-negative integers.

Java’s addElement is equivalent to Water’s insert except that Water can insert any Water object into an object treated as a vector, whereas Java’a vectors can only take official Java objects, An “int” is not an official Java  object, yet it is necessary for doing arithmetic in Java.  You can convert an int into an integer using new Integer(37) and stick it into a vector. But note you cannot do arithmetic on an Integer so it is not very useful except as a means of “treating it as a mostly useless object”.

Once we have our Integer in a Vector we can get it back out again with Java’s “elementAt” method. In Water we can just use the dot notation since we are just referencing a field whose key is 0.

In Java, once we have our Integer out of the vector we must call the intValue on it in order to get the actual int to do something with it. But in order to call that method, we have to cast the Integer we got out of the vector into what it already is, i.e. an Integer.

Furthermore, if we wanted to decide whether our Vector was really holding what we wanted, an int vs an Integer, well, too bad. We lost that information.

In Water we are storing just what we want and there is no “int” and “Integer”, just “integer”.

The Java code uses 94 characters. The Water code uses 33 characters to accomplish the same task as far as the computer is concerned. As far as conveying the semantics of the algorithm, well, you be the judge.  In the next example the difference between character counts and clarity is potentially even greater.

Java cannot take unlimited args
public static Bob make_bob() {
       //fn body
   }
   public static Bob make_bob(Object key1, Object value1) {
      //fn body 
   }
   public static Bob make_bob(Object key1, Object value1, 
                              Object key2, Object value2) {
      //fn body;
   }

Water can take unlimited args
<defmethod make_bob rest=*optional>
       <!-- loop over rest arg -->
</defmethod>

Method Calling Flexibilities

Because Java does not permit the passing of unlimited number of arguments to a method, you often must write several variations of the same basic function to simulate the effect of unlimited arguments. Of course if the user passes in more arguments than methods that you have defined, too bad. A Java programmer is reluctant to create lots of such methods however not only because it is more typing, source, etc., but because if there is a bug in one or you need to extend the basic method, you’ve got to change the similar code in all the methods. You can stick the similar code in some “helper” method but then that’s even more methods you’ve got to have and it doesn’t help with changes like respelling a parameter name.

One workaround in Java is that you can design your method to take a vector of values. But then the caller has to create the vector and pass that in instead of passing in the real values directly. This makes the call more complex and introduces all the problems of vectors described in the previous example. Since the major reason for creating methods in the first place is to hide implementation details like this, it is a serviceable but mediocre work-around.

Water solves all these problems in one fell swoop with a “rest” parameter. During a call to the method, “rest” is a local variable bound to a list of the incoming arguments which is convenient to loop over and process each one with just one hunk of code.

In addition to “rest” arguments, Water also has “optional” arguments. You supply default values for each optional argument when you are defining the method.  If you don’t pass in an optional argument, the default value is used. You can pass an argument by keyword, i.e. naming it (as well as by-position), it is easy to specify just those arguments that you care about and let the rest default.

 In Java, all arguments are required making it much harder to write flexible methods that can be reused in a variety of situations.  Since Java doesn’t permit passing arguments by keyword, its harder to write self-documenting code.

In Java you can't write a method on a system class
public class String { public boolean has_spaces () {
     
//code here } }

 “my string”.has_spaces()   illegal

Water is not so restrictive


string.
   <defmethod has_spaces>
       <!-- code here -->
            </defmethod>

 my_string.<has_spaces/>   legal

Modularity and Flexibility

In Java you just cannot add a method to a class for which you do not have the source and therefore can’t recompile. Java let’s you subclass some class such as “String” and you can add whatever methods you want to that. But if you’re using Java’s native string functions that produce strings, that new method is no good to you.

Water let’s you create a new method on any class regardless of whether you have the original source or not. Thus the user can effectively extend the system software and get maximum utility out of the system software.  Water gives you the capability of grouping your functions in files however you want. Of course it behooves the Water programmer to be disciplined. Java’s “all methods of a class must live in the one class file” approach is a good general rule to follow, and you can modularize that way in Water if you like. But the more flexible modularity freedom of Water gives the option to the programmer which can help with complex organization problems.

Java Test Harness
public void test_true (Boolean val, String source) {
   if(val != true) {  
      System.out.println(
        "Busted: " + source);
    }
}
test_true(hairy_fn(), "hairy_fn()");

Water Test Harness
<defmethod test_true source=required="ek_string">
 <try <if> source.<execute_string/>.<not/>  
             <concat "Busted: "source/>.<print/>
           else “OK”
      </if>

   >
  <concat "Errored: " source/>.<print/>
 </try>
</defmethod>

<test_true <hairy_fn/> />

Writing Tools

In Java all arguments to a method are evaluated at method call time. There are a few special system functions like IF that delay evaluation but these are built in to the guts of Java and can not be defined in Java itself. Furthermore the user cannot write methods that do not evaluate their arguments. So what? Well it just makes lots of reasonable things to want to do in a language impossible to do in Java.

The first example of the limitations of Java’s “eval all arguments” behavior is demonstrated in the problems with writing a test harness in Java. You want to evaluate some code and, if the code does not return true, print out an error message indicating which code did not evaluate as you had expected. For any one such call it is easy to get around it as I have done in the Java example by passing TWO arguments to the test_true method, one the actual value of the code to be tested, and the other the error message to print if the code failed. But we need to write, potentially thousands of such calls when testing a large system. Not only does the retyping or copy and pasting get tedious, but when you want to make a change to the code to eval, you must be careful to update the “message” argument, lest you may not be able to find which actual call caused the bug. Thus the Java test harness itself introduces another significant source of bugs, bugs that just confuse finding the real bugs. (I have personally experienced this very problem many times.)

In Water we can get around this source of bugs by writing a function which does not evaluate its argument at function call time. Rather, the Water “string” execution kind just grabs the source code of the argument being passed in as a string [with no parsing or evaluation]. Then this one string can be used in two ways, directly to print out the error message, and as source code to evaluate for the actual test itself. Our calls to test_true now take one argument instead of two with no possibility of introducing an inconsistency. Note that this example also shows how you can call the Water evaluator from Water code, something you cannot do in Java. Furthermore we are able to distinguish between source code that errors and source code that just returns false. Passing Java code that errors into our Java method actually errors before we get inside our method giving our method no chance to print a nice error message.

Cache Method Calls

Water has execution kinds. Java does not 
 thing.<set the_cache=<thing/>/>
 <defmethod cache_it expr=required="ek_string">
      <if> the_cache.<has_key expr/> 
           the_cache.<get expr/>
 	 else
 	   <set val=expr.<execute/>>
                 the_cache.<set_value expr val/>
                 val
             </set>
      </if>
 </defmethod>
 <cache_it 2.<plus 3/> /> 
 

Delayed Evaluation of Arguments

Using Water’s capability to delay evaluation, we can write a method that, upon receiving a given argument for the first time will execute it, save its value in a cache and return the value. Upon receiving the same argument a second time, it will just return the cached value. For an example like the call to “plus” above, this does not buy us much since plus is so quick to execute. But imagine a compute-intensive method, or worse, going over the net to retrieve a value. A method like cache_it could save a LOT of compute time.

Here I did not bother to show a Java version of cache_it. It would have the same problems as the test example above. Here’s how our Water method works. First we initialize a “global variable” in the root object named “the_cache” to an empty record. Then, during each call to cache_it we see if the_cache contains a field whose key is the source passed in to cache_it. If so, we return value stored in that field. Otherwise we execute the source, save its value in the_cache under the key of the source itself and return the value.

For compute-intensive methods,  cache_it may be able to speed up a program hundreds of times. In Java you might be able to write such a method that was customized to particular method calls that took a restricted set of arguments. But in Water, our one cache_it method is good for any method calls (or any other valid water expression for that matter) that we could ever write in Water. Actually there is a workaround for Java’s limitation here. Since you can call Java methods from Water, we could actually use our cache_it for caching calls to Java methods that are made within Water.

Hypertext

Here we are defining b to the “bold” tag in HTML. It takes just one argument, its “content” argument. This arg is declared to have an evaluation kind of hypertext. When a call to bold is made, i.e.

Water's execution kind is useful for hypertext, too.
 <defclass b content=optional="ek_hypertext">… </defclass>
 <b>   this  <i> is </i> real </b> 
 

bold’s one argument,  “this  <I> is </I> real”, is converted in to a list of three values, a string, an italic object and a final string because that is what the execution kind of hypertext does. This makes it trivial to define existing and new HTML tags. In fact, we use this to define the HTML library Water. By embedding other Water calls within the hypertext we can very easily get “active” content using HTML-like syntax.

Extensions to Java allow it to parse and walk a DOM (Document Object Model) of XHTML or any other XML document. But  then you have a program with not just two parsers (the Java parser and an XML parser) but  two different object systems as well, (the Java object system and the DOM). In Water there is one parser, one object system and a much more intimate connection between HTML, XML and the programming language than is possible in Java. Water makes it especially convenient to mix XML structures like XHTML and active code.

The above examples might not be functionality that every user would write. But they represent something that can easily be implemented by system programmers or third party developers that many users could take advantage of. The flexibility of execution kinds dramatically increases the kinds of methods you can write enabling higher level methods with easier calling syntaxes than are possible in most languages, including Java. Furthermore it demonstrates what can be done with a language that’s actually designed to integrate with HTML and XML as opposed to bolted on the side.

Multi-Role Object Systems

Water’s object system is a whole lot more flexible and dynamic than Java’s.
Above there’s an example of adding a method to a system class, something that’s impossible in Java. Water instances can also have fields added to them even if their class does not define such a field. This capability includes adding instance-specific methods if you like. It takes discipline to not abuse this power, but the flexibility makes it easy to add instance-specific information to instances without having to create a whole new class.

Although Water has a “defclass” method, you can actually use any object, even an instance as the “parent” of another object. The ability to allow any object to effectively become a class means that you don’t have to make up classes that somehow contain default values that are dynamically computed. (Probably this can be done in Java but it is very complicated and not something the average programmer would ever do.) In Water its easy. In fact you can even change the parent of an object on the fly just by setting it to a new value using the same “set” method that’s used to set any field in an object.

Water instances can be used as “classes” but Water classes can be used as “instances”. There is rarely a need to invoke the “singleton” pattern in Water since classes are objects very similar to their instances. In fact, giving a class the right default values let’s you treat it just as a “prototypical” instance.

Development Environment

Water’s development environment uses the dynamism of Water to reduce the time of the edit, compile, debug cycle. In fact, there is no “compile” stage as in Java. You can select any piece of source code in the editor buffer and click the Execute button and see the result immediately. There is no need to write a “main” method or write a testing structure just to see what some code will evaluate to.

The Water development environment uses a “test” method that’s similar to the one in the above example but even easier to use. You select a piece of source code, choose “test” off of a menu and a unit test is made for that piece of code that includes the value that executing the source returned.  The combination of Water’s execution kinds and dynamic environment makes this possible.

Water also has a documentation system where you don’t need to learn another language a la “Java Doc” to create documentation. Again Water’s execution kinds help to make “content” that is not executed, and you are not burdened with sticking it in a comment like you must do in Java.

Water uses its uniform and flexible syntax and semantics to provide easy access to web services, sql, regular expressions and a host of other functionalities that Web programmers need. That level of integration is not possible in a language with the restrictions of Java.

© Copyright 2001-2003 Clear Methods, Inc. All rights reserved.