Skip to main content

skip to main content

developerWorks  >  Java technology  >

alt.lang.jre: Twice as Nice

Learn how this highly expressive language is a safe bet for the Java platform

developerWorks
Document options

Document options requiring JavaScript are not displayed


New site feature

Check out our new article design and features. Tell us what you think.


Rate this page

Help us improve this content


Level: Introductory

Andrew Glover, CTO, Vanward Technologies

06 Oct 2004

Nice is a JRE compatible, object-oriented language that brings tremendous expressiveness to the Java platform. Nice also lets you implement many of the cutting edge features found in Java 5 on any Java virtual machine. In this fourth installment of the alt.lang.jre series, regular contributor and all around "Nice" guy Andrew Glover walks you through some of the most exciting features of Nice.

Nice is an object-oriented, JRE-compatible programming language that has been formulated with an emphasis on modularity, expressiveness, and safety. Unlike the Java language, which is purely object oriented, Nice incorporates functional and agile development techniques, including some of those found in aspect-oriented programming. Like many of the newer development languages, Nice has the advantage of building on the shortcomings of its predecessors, including the Java language. What's more, Nice offers many of the same features found in Java 1.5, but makes them available on any JVM.

About this series

While most readers of the alt.lang.jre series are familiar with the Java language and how it runs on a cross-platform virtual machine, fewer may know that the Java Runtime Environment can host languages besides the Java language. This series of articles is a survey of many of the alternate languages for the JRE. Most of the languages explored here are open source and may be used for free, while a few are commercial products that must be purchased. All of the languages introduced in this series are supported by the JRE and are believed by the authors to enhance the dynamic and flexible nature of the Java platform.

In this fourth installment of the alt.lang.jre column, I'll introduce you to the most interesting and useful features of Nice, including parametric classes, design-by-contract constructs, multimethods, and much, much more.

Getting started with Nice

In the Java language, the central unit of functionality is embodied in classes. Nice, however, moves this notion up one level to packages. Nice lets you define multiple classes, interfaces, and methods in one file ending with a .nice postfix. Once a file has been so defined, it typically becomes a package. Nice's compiler, nicec, is used to compile .nice files and is available as both an Ant task and an Eclipse plug-in (see Resources).

Running a Nice application requires the definition of a main() method. You define this method just as you would a main() method in the Java language. But Nice's main method differs from the Java language method in that it is defined in a package, not a class. This method can therefore become a static hook to jumpstart a Nice application, much like a static main() method in a Java class allows you to run a class from the command line.

You can see how Nice's main() method works in Listing 1, which is a simple application that verifies some basic math knowledge. Notice that Nice supports the assert keyword, even though I'm running on a pre-1.4 VM.


Listing 1. A simple Nice program
void main(String[] args){ 
   assert 4 + 4 == 8;
}

In Nice, as you'll see shortly, methods can be defined outside of classes. The implications of defining methods at a package level will become clearer as you read.



Back to top


Nice plays it safer

Code safety is one of the most powerful features Nice can bring to your development toolbox. Nice is particularly effective when it comes to dealing with two very common Java exceptions: the underhanded ClassCastException and NullPointerException.

These two exceptions have posed a longtime challenge for developers working on the Java platform. Java 5.0 incorporates generic types as an effective means of addressing ClassCastExceptions, but this adaptation will do little for many corporations still using Java 1.3. Nice, on the other hand, was developed with ClassCastExceptions and NullPointerExceptions in mind. As such, the language supports two features, parametric classes and optional types, that go a long way toward keeping applications from throwing these exceptions. What's more, with Nice you can employ these features today, on any Java platform 1.2 or higher.



Back to top


Parametric classes

In the Java language, all collections hold the least-common-denominator type, which is Object. As a result, the Java developer must cast types every time he or she retrieves an element, which can be a burden in larger programs. To demonstrate the problem, I'll show you how the Java language deals with types, and then how Nice's parametric classes simplify the issue.

Listing 2 shows an IStack interface, representing a stack data structure defined in the Java language.


Listing 2. The IStack interface defined in the Java language
public interface IStack {
  
  int size();
  
  boolean isEmpty();
 
  Object pop();
  
  Object peek();
  
  void push(Object obj);
}

This Java implementation of IStack is quite simple, but still works as a stack data structure, as shown in Listing 3.


Listing 3. The IStack Implementation
import java.util.ArrayList;
import java.util.List;

public class SimpleStack implements IStack {
  private List items;

  public SimpleStack(){
   this.items = new ArrayList();
  }

  public int size(){
    return this.items.size();
  }

  public boolean isEmpty(){
    return this.items.isEmpty();
  }

  public Object pop(){
    return this.items.remove(this.items.size() - 1);
  }

  public Object peek(){
    return this.items.get(this.items.size() - 1);
  }

  public void push(Object obj){
    this.items.add(obj);
  }
}

While fairly effective, this stack implementation requires tedious type casts whenever I utilize it, as demonstrated in Listing 4.


Listing 4. JUnit example showing tedious type casts
import junit.framework.TestCase;

import org.age.nice.examples.stack.IStack;
import org.age.nice.examples.stack.SimpleStack;


public class SimpleStackUseTest extends TestCase {
  private IStack stack;

  public void testSimpleIntegerPops(){
    this.stack.push(new Integer(1));
    this.stack.push(new Integer(2));
    Integer two = (Integer)this.stack.pop();
    TestCase.assertEquals("value should be 2", 2, 
	   two.intValue());
  }

  public void testSimpleIntegerPopsWillNotCompile(){
    this.stack.push(new Integer(1));
    this.stack.push(new Integer(2));		
    //Integer two = this.stack.pop();		
    //TestCase.assertEquals("value should be 2", 2, two.intValue());
  }

  public void testPopClassCastException(){
    this.stack.push(new Integer(1));
    this.stack.push(new Double(2.0));
    try{
      Integer two = (Integer)this.stack.pop();
      TestCase.fail("top item was successfully cast to Integer!");
    }catch(ClassCastException e){
      //ignore expected behavior
    }		
  }

  protected void setUp() throws Exception {
    this.stack = new SimpleStack();
  } 
}

In Listing 4, the testSimpleIntegerPops() method shows how you must cast the result of the pop operation to an Integer. If you fail to perform a cast, you produce a ClassCastException, as demonstrated in the testPopClassCastException() method. Look closely at the commented out lines of the testSimpleIntegerPopsWillNotCompile method. They won't compile with normal Java code; but doesn't that code look nice?

Nicer collections handling

Parametric classes, or templates, if you come from a C++ background, are commonly used to simplify collections handling, among other functions. Parametric classes let you define a single, precise type that the collection can then hold.

To define a generic Stack implementation in Nice, you use the <Type> syntax when defining the class, as well as for desired parameters and return types. In Listing 5, you can see how you could redefine and simplify your stack data structure with a SimpleStack class using Nice's <Type> syntax.


Listing 5. A Nice parametric stack
class SimpleStack<Type> {
  List<Type> items = new ArrayList();

  void push(Type t)  {
    items.add(t);
  }
 
  Type pop(){         
    return items.removeAt(items.size() - 1);
  }	
 
  Type peek(){
    return items.get(items.size() - 1);
  }
 	
  boolean isEmpty(){
    return items.isEmpty();
  }
 
  int size(){
    return items.size();
 } 
}

The Nice parameterized SimpleStack class is now defined to hold specific types, defined at compile time, not at runtime as in the Java language. Notice how in Listing 5 both the pop() and peek() methods return Type, and how the push() method takes Type as a parameter. This Type matches the object type that the internal items collection will hold.

Listing 6 shows how a parametric stack is used in Nice. Note that I've created a SimpleStack instance that can hold only objects of type String. Notice how the last line, which attempts to push an Integer onto the parameterized stack, won't compile in Nice!


Listing 6. Using a parametric stack in Nice
 void main(String[] args){ 
    
   SimpleStack<String> stk = new SimpleStack();
   stk.push("Groovy");
   stk.push("Ruby");   

   assert stk.pop() == "Ruby";   
   assert stk.peek() == "Groovy";

   //following line won't compile! 
   //stk.push(new Integer(1));
 }



Back to top


Optional types

The NullPointerException is probably the most familiar exception for any Java developer. Indeed, null pointers are so nefarious that even the simplest Java compilers can flag objects as not having been initialized. Unfortunately, you may have discovered at one time or another, those warnings do not catch every possible occurrence of null.

In keeping with its touchstone property of safety, Nice provides the notion of optional types. Because optional types are defined by the author of the API in the Java language, they are often difficult to determine, so Nice prefixes them with a question mark (?).

Optional types are particularly useful if you've ever found yourself developing to an API that takes numerous parameters. You've most likely determined that various parameters are optional either because the JavaDoc comments specify them as such or because you've passed in null and noted that things appear to work.

With Nice, you could simply add a ? before a parameter to signify that the variable is capable of being null; conversely, variables without the question mark cannot be explicitly set to null. You can see how this works in Listing 7, where I've defined a method, walk(), in the Dog class. This signifies that the second parameter of type Location is capable of being null.


Listing 7. Defining an optional type in Nice
class Leash{
  int length;
}

class Location{
  String place;
}

class Dog{
  String name;

  void walk(Leash leash, ?Location location){

    var place = (location == null)? 
	    " no where " : " to " location.place;

    println("walking " name " 
      with a leash " leash.length " inches long"
       //won't compile-> location.place   
       place);
  }
}

Notice how in Listing 7, the walk() method must now account for the fact that location can be null. In the println() method, the compiler will not allow the code to actually reference location.place.

Listing 8 offers a demonstration of the effectiveness of optional types, in this case using the walk() method defined in Listing 7. Notice that you can now legally pass in null and you can also legally pass in a valid value for location.


Listing 8. Demonstration of optional types in Nice
Leash lsh = new Leash(length:35);   
Dog mollie = new Dog(name:"Mollie");

mollie.walk(lsh, null);

Location loc = new Location(place:"The Coffee Shop");

mollie.walk(leash:lsh, location:loc);

Do not confuse the null capability syntax with optional parameters, which are quite different.



Back to top


Named and optional parameters

As you probably noticed in Listing 8, Nice allows you to specify parameters when invoking a method. When I invoked the walk() method on the Dog instance, I explicitly named the parameters. For example, I set the lsh variable of type Leash to the first parameter, leash.

Nice also lets you specify parameters in a constructor, much like you can in languages such as Groovy and Jython. In Listing 8, when I created new instances of Dog and Leash, I set each instance's properties, name, and length respectively, explicitly in the constructors.

Naming parameters allows you to pass them in any desired order. In Listing 9, for example, both calls are fundamentally the same; because I've named the parameters it doesn't matter what order I pass them in.


Listing 9. Further demonstration of optional types in Nice
mollie.walk(leash:lsh, location:loc);
//same behavior from walk method
mollie.walk(location:loc, leash:lsh);

Optional parameters are even more useful than optional types. They can actually work in place of optional types. Listing 10 shows how I could redefine the walk() method using optional parameters.


Listing 10. Optional parameters in Nice
void walkAgain(Leash leash, Location location=new Location(place:"nowhere")){
  println("walking (again) " name " with a leash " leash.length
     " inches long to " location.place);
}

This code defines the second parameter, location, as being optional. When the method is invoked, this parameter does not have to be passed in. If no value is passed in, the default value will be used. In this case, the default value is a Location with a place equal to nowhere.

Optional parameters free you of the need to program defensively, as i did in Listing 7. Unlike the walk() method in Listing 7, the walkAgain() method of Listing 10 need not be written around the possibility of encountering null values.

The final thing to note about optional parameters is that they can be overridden with other values. As Listing 11 shows, I am able to call walkAgain with a choice of one or two parameters.


Listing 11. Overriding optional parameters in Nice
mollie.walkAgain(lsh);
Location locBY = new Location(place:"the backyard");
mollie.walkAgain(lsh, locBY);



Back to top


Design by contract

Design by contract (DBC) is a technique for ensuring that all the components in a system do what they're meant to do, by explicitly stating each component's intended functionality and expectations of the client in its interface. Eiffel (see Resources) is a popular language utilizing DBC. Its techniques have been incorporated by many languages, including Java 1.4, which introduced the use of assertions. Nice uses the keywords requires and ensures to incorporate contractual information in your programs and assertions to validate the state of that programs during execution. Additionally, Nice supports the assert keyword even for pre-1.4 JVMs.

The requires and ensures keywords function like preconditions and postconditions, allowing you to define the conditions that desired methods will act upon and guarantee. An unmet condition will result in the generation of assertion exceptions at runtime. The combination of conditions and assertions has saved many an application from sinking into the deep hole that can be caused by logical errors. I'll show you how these two mechanisms work together in the examples that follow.

Conditional use

The requires clause states the requirements that must be met by the client of a method. If the requirements are not met, the method will be abandoned and an assertion exception generated. The ensures clause works on the side of a method's client. This clause is the guarantee the method commits to its associated caller.

Listing 12 defines a CoffeeMachine class that contains a brew() method. In the definition of brew(), I've stipulated that clients must pass in a Coffee instance with a beanAge property less than 10. Otherwise, an assertion exception will be generated with the clause "Beans are too old to brew." Moreover, I've also guaranteed that the result of the method, in this case an instance of CoffeeCup, will have a temp property greater than 155.


Listing 12. DBC ensures an ideal brew
class CoffeeMachine{

  CoffeeCup brew(Coffee cfe) 
    requires cfe.beanAge < 10 : "Beans are too old to brew"
    ensures result.temp > 155 : "Coffee isn't hot enough to serve" {

      return new CoffeeCup(coffee: cfe, temp:160, isFull:true);
   }
}

class Coffee{
  int beanAge;
}

class CoffeeCup{
  int temp;
  boolean isFull;
  Coffee coffee;
}

Listing 13 shows a new CoffeeMachine instance along with a new instance of Coffee. Notice that in this case I've set the Coffee property of beanAge to 15, which is, of course, greater than 10 and will consequently not meet brew's contract.


Listing 13. A violation of contract
CoffeeMachine machine = new CoffeeMachine();
Coffee cfe = new Coffee(beanAge:15);
CoffeeCup cup = machine.brew(cfe);

Listing 14 demonstrates the exception stack that is generated when brew() is called in Listing 13. As you can see, the customized message "Beans are too old to brew" is present to facilitate debugging.


Listing 14. These beans are too old to brew!
Exception in thread "main" nice.lang.AssertionFailed: 
  Beans are too old to brew
  at test.dispatch.brew(MoreCoffee.nice:6)
  at test.fun.main(HelloWorld.nice:111)
  at test.dispatch.main(MoreCoffee.nice:0)

Nice assertions are not enabled by default, so they must be explicitly turned on. See Nice's documentation for directions specific to your JVM.

Stack revisited

With a basic understanding of how Nice implements preconditions and postconditions, let's look at what happens when I apply these techniques to the earlier IStack example. In Listing 15, I've defined an IStack interface in Nice and added various ensures and requires clauses.


Listing 15. The IStack interface with conditions added
interface IStack<T>{
  int size() ensures result >= 0 : "size can not be less than one";
	
  void push(T t) ensures size(this) > 0 : 
    "pushing an item should increase the size";	
	
  boolean isEmpty() ensures result == (size(this) == 0) : 
    "if size is zero, result should be false";	

  T pop() requires !isEmpty(this) : "Can not pop an empty stack";

  T peek() requires !isEmpty(this) : "Can not pop an empty stack";
}

In Listing 16, I've implemented the IStack interface. Notice that Nice gives you a shortcut for defining method bodies: you simply use the = syntax. Also note the use of Nice's override syntax in the example below.


Listing 16. The new and improved stack
class DBCStack<T> implements IStack{

  ArrayList<T> contents = new ArrayList();

  override void push(T t) = contents.add(t);
  
  override T peek() = contents.get(contents.size() - 1);    
  
  override T pop() = contents.removeAt(contents.size() - 1);  
  
  override boolean isEmpty() = contents.size() == 0;
    
  override int size() = contents.size();

}

For the most part I can use the new DBCStack the same as before. If I attempt to violate the terms of IStack's contract, however, it'll cause assertion exceptions. In Listing 17, for example, you can see what happens when I attempt to push a third item on a stack that has been ensured for only two items. The third pop() invocation causes the precondition, requires !isEmpty(this), to fail. Consequently, an AssertionFailed exception is generated with the custom message: "cannot pop an empty stack."


Listing 17. Testing the limits of the new stack
let IStack<Dog> dbcStack = new DBCStack();

dbcStack.push(new Dog(name:"Stella"));
dbcStack.push(new Dog(name:"Mollie"));
    
println(dbcStack.pop().name);//mollie
println(dbcStack.pop().name);//stella

// throws assertion error -> println(dbcStack.pop().name);
// Exception in thread "main" nice.lang.AssertionFailed: 
// Can not pop an empty stack
// Can not pop an empty stack
/   at test.dispatch.pop(StackImpl.nice:10)
//  at test.fun.main(HelloWorld.nice:129)
//  at test.dispatch.main(StackImpl.nice:0)



Back to top


The joy of multimethods

One of the most interesting and unique features of Nice is multimethods, or the ability to define a class instance method outside of a particular class's definition. This feature alone creates an abundance of extensiveness comparable to some of the more exciting tenets of aspect oriented programming (AOP).

A multimethod's syntax is quite easy, because the first parameter is the type to which the method should be affixed. Remaining parameters become the standard parameters to the instance method. To illustrate, in Listing 18, I can create a simple, meaningless method and attach it to instances of java.lang.String.


Listing 18. Multimethods in Nice
void laugh(String str){
  println("haha, I'm holding an instance of " str);
}

In this code, the laugh() method simply prints a String when invoked. Because the first parameter to the laugh() method is of type String, the method is therefore attached to instances of String. In Listing 19, I create a String instance, myString, and invoke the laugh() method, which prints "haha, I'm holding an instance of Andy."


Listing 19. Using Multimethods in Nice
let myString = new String("Andy");
myString.laugh();

While the laugh() method is completely useless, it does illustrate a few key points:

  • Nice lets you easily affix new behavior to objects.
  • Nice allows you to attach this behavior to anything, including standard classes whose source code you cannot access.
  • Nice lets you add behavior to final objects.

Advanced multimethods

Adding useful behavior to objects is more fun than toying with useless ones, so let's take our knowledge of parametric classes and multimethods to the next level. In Listing 20, you'll see what happens when I add a join() method to the java.util.Collection interface. The join() method is quite common among agile languages; it simply appends a desired String to all elements in a collection, thus creating a large String.


Listing 20. Adding a join method to the Collections interface
/**
 * multi method, adds a join call to a collection. 
 * @return a string like 1-2-3-4. 
 */
<T> String join (Collection<T> collection, String value = " "){
  StringBuffer buff = new StringBuffer();
  let size = collection.size();
  var x = 0;
  for (T elem : collection){
    buff.append(elem);
    if(++x < size){
      buff.append(value);
    }
  }
  return buff.toString();  
}

Listing 20 demonstrates how you can use Nice's multimethods functionality to attach a join() method to any typed Collection. In this case, the join method has an optional parameter: the String with which you will join the elements of the Collection instance on which the method is called.

Listing 21 shows how effortless it is to use the new join() method. I simply pass in the desired join String or use the default. This functionality is just like static crosscutting in AOP, but the Nice version is arguably much easier!


Listing 21. A nice new join method!
Collection<int> nColl = new ArrayList();
nColl.add(1);
nColl.add(3);
nColl.add(3);

println(nColl.join("**")); //prints 1**3**3
println(nColl.join()); //prints 1 3 3



Back to top


Abstract interfaces

In addition to multimethods, Nice offers a second means to affix additional behaviors to objects. Abstract interfaces are similar to normal Java interfaces, but far more flexible. The nicest thing about abstract interfaces is that they can be implemented by any object, even after they've been defined. In this regard, abstract interfaces function much like static crosscutting in AOP.

In Listing 22, you can begin to get an idea of how abstract interfaces work. I start by creating a new abstract interface of type TasteTest with one method, taste(). I then proceed to make the Mocha and Latte classes implement this new type.


Listing 22. Abstract interfaces in Nice
package test;

class Latte {
  getPrice() = new BigDecimal(2.50);
}

class Mocha {
  getPrice() = new BigDecimal(4.30);
}

abstract interface TasteTest{
   void taste();
}

class test.Mocha implements TasteTest;
class test.Latte implements TasteTest;

taste(test.Mocha mcha) = println("Ohh this is good...");
taste(Latte lte) = println("Waking me up it's soooo good.");


Using the new functionality on instances of Latte is quite simple, as shown in Listing 23. I simply call the taste method!


Listing 23. Latte wake up call
let coffee = new Latte();	
coffee.taste(); //prints Waking me up it's soooo good.

While the examples using multimethods and abstract interfaces are necessarily quite brief, they do demonstrate the level of expressiveness that can be achieved using Nice. In fact, some would argue that Nice's expressiveness, bolstered by the simplicity of its syntax, rivals that of AOP in Java programming.



Back to top


Nicely enumerated

As previously noted, Nice incorporates some features found in Java 5.0, but enables you to use them now on virtually any Java platform. One of these features is enumerated types. Like parametric classes, enumerated types can assist in bug detection at compile time, rather than at runtime.

To understand how enumerated types work, I'll use a common development example. Constants or keys are commonly placed in interfaces and classes as static final fields. Other classes then reference these fields, rather than local variables, in an attempt to limit variation. We've all seen code that defines constants like those found in Listing 24.


Listing 24. Example Java constants
public class CoffeeBeans {
    public static final int ESPRESSOROAST = 1;
    public static final int KONA = 2; 
    public static final int FRENCHROAST = 3; 
    public static final int MOCHA = 4;
}

Listing 25 shows a typical example of constant types in action. If you study the code carefully, you'll also see where their effectiveness is compromised.


Listing 25. The limits of Java constants
public static void brew(int coffeeType){    	    
  if(coffeeType == CoffeeBeans.ESPRESSOROAST){
    System.out.println("brewing espresso!");
  }  
  //other if/else clauses....
}

The problem with the code in Listing 25 is that villainous or just plain ignorant clients can call the brew() method with values not within the defined bounds. For example, if I call the method with 48, the code will compile perfectly. Unfortunately, I'll still have to deal with the defect when I run the code.

Using enumerations rather than constants would make this code safer. Enumerations would force the compiler to, in essence, guarantee values within defined bounds. For example, in Listing 26, you can see what happens when I define an enumeration for CoffeeBeanTypes, as well as defining an interface of type ICoffee and two implementations: Latte and Mocha.

As you can see, these ICoffee types define a getType() method that returns an instance of the enumeration. You can also see what happens if I define a CoffeeMachine class with a brew() method that takes an instance of the enumeration, rather than an int as shown in Listing 25.


Listing 26. Defining enumerations in Nice
enum CoffeeBeanType(String value){ 
  ESPRESSOROAST("espresso"), 
  KONA("kona"), 
  FRENCHROAST("French Roast"), 
  MOCHA("Mocha Java")
}

interface ICoffee{
  CoffeeBeanType getType();
  BigDecimal getPrice();
}

class Latte implements ICoffee{
  getType()= ESPRESSOROAST;
  getPrice() = new BigDecimal(2.50);
}

class Mocha implements ICoffee{
  getType()= FRENCHROAST;
  getPrice() = new BigDecimal(4.30);
}

class CoffeeMachine{
}

void brew(CoffeeMachine machine, CoffeeBeanType type){
 println("Brewing a coffee with " type.value " beans" );
}

In Listing 27, I use the enumerated type when calling the brew() method on the instance of CoffeeMachine. Notice how enumerations in Nice implicitly contain a value field. In the case of ESPRESSOROAST, the value is the String that was passed in on creation: espresso.


Listing 27. Using enumerations in Nice
let cfe = new CoffeeMachine();
cfe.brew(ESPRESSOROAST); 
//prints Brewing a coffee with espresso beans
	
let coffee = new Latte();	
println("\n My latte cost me $" coffee.getPrice() 
  " and is brewed with " coffee.getType().value " beans");



Back to top


Advanced collections handling

Nice is a strongly typed language that eschews the notion of casting objects, so all collections in Nice must be parameterized. For example, the line below will compile normally in the Java language, but not in Nice, where the collection must be more strongly typed.

Collection noColl = new ArrayList(); //will not compile in Nice

If for some reason you need to render a Nice collection more Java-like, you can simply parameterize it with java.lang.Object, as shown below.

Collection<Object> collObj = new ArrayList();

Multimethods and collections

Like some other languages, Nice has added a host of additional methods onto the standard collections, using multimethods. Nice's collection behaviors are similar to those found in Groovy and Ruby in that they support a block-like syntax. While these code blocks are really just anonymous methods and not nearly as powerful as true closures, they're still quite handy.

Nice provides a slick iterator-like method on collections aptly named foreach(), as shown in Listing 28. Notice how the foreach() method takes a block of code signified by the =>. In this case, the block simply prints the incoming value of i.


Listing 28. The foreach method on collections
Collection<Integer> coll = new ArrayList();
coll.add(new Integer(1));
coll.add(new Integer(2));
coll.add(new Integer(3));

coll.foreach(Integer i => {
  println(i);
}); //prints 1 2 3

Nice has also enhanced the Java language's Set interface with a handful of powerful methods, as demonstrated in Listing 29.


Listing 29. Powerful Set methods in Nice
Set<int> testSet = new HashSet();
Set<int> otherSet = new HashSet();

testSet.add(1);
testSet.add(2);
testSet.add(3);

otherSet.add(3);
otherSet.add(4);
otherSet.add(5);

Set<int> nSet = testSet.intersection(otherSet);   
nSet.foreach(int i => println(i)); //prints 3   

Set<int> uSet = testSet.union(otherSet);   
uSet.foreach(int i => println(i)); //prints 1,2,3,4,5

Set<int> difSet = testSet.difference(otherSet);   
difSet.foreach(int i => println(i)); //prints 1,2

Set<int> dSet = testSet.disjunction(otherSet);   
dSet.foreach(int i => println(i)); //prints 1,2,4,5

As you can see in the code above, Nice provides an intersection() method that finds common elements in two separate Sets. The union() method combines two Sets, while the difference() method finds the difference between a pair of Sets. Lastly, the disjunction() method combines two Sets, dropping common elements.



Back to top


Additional features

I'll close this introduction to Nice with a look at three of its more interesting convenience features: value dispatching, enhanced for loops and ranges, and more relaxed String usage. As in the previous sections, you'll notice that each of these features brings greater expressiveness and modularity to your code while also enhancing its safety.

Value dispatching methods

Value dispatching methods are used to ensure that the runtime decision of which method to actually call or dispatch to is not only determined by a parameter's type, but by its actual value. Such methods can help you avoid code that contains a series of if/else clauses or switch statements.

You can see how this works in Listing 30. I start by defining an enum for music genres. I then create a series of value dispatch methods which, in effect, simulate a switch statement. If I then pass in a Genre of value Celtic, the variationName() method will return "Irish," as shown below.


Listing 30. Value dispatching defined Nicely
enum Genre(String value) { 
  Celtic("Celtic"), Rock("Rock"), Folk("Folk"), 
    Jazz("Jazz") 
}

String variationName(Genre gre);
variationName(gre) = "No variations Available";
variationName(Celtic) = "Irish";
variationName(Folk) = "Acoustic";
variationName(Rock) = "Pop";
variationName(Jazz) = "Smooth Jazz";

As shown in Listing 31 below, when I pass in the Genre enum of value Folk, the resulting variation variable is set to Acoustic.


Listing 31. Using value dispatching in Nice
var variation = variationName(Folk);
println(variation); //prints Acoustic

Enhanced for looping and ranges

You've probably already noticed by now that Nice supports a shorthand notion of the standard for loop, much like the new Java 5. A simple for loop construct is such a common facet among agile languages that it's a wonder the Java language has taken so long to introduce it!

As shown in Listing 32, Nice lets you easily iterate over a collection of ints without having to use the Java language's normal Iterator interface. Also note that Nice supports autoboxing, much like Groovy (see Resources for the alt.lang.jre installment on Groovy).


Listing 32. A Nice for loop
Collection<int> iColl = new ArrayList();
iColl.add(11);
iColl.add(12);

for(int i : iColl){
  println(i);
}	

Listing 33 demonstrates range functionality in Nice. Nice supports inclusive ranges only; consequently, in the code bellow, the numbers 1 through (and including) 20 are printed.


Listing 33. Inclusive range in Nice
for(int i : 1..20){
  print(i);
}

Relaxed strings

Nice provides normal Java Strings, but relaxes some of the constraints associated with their usage in the Java language. If you've worked with Python, you'll recognize Nice's multiline string literals. In Listing 34, you see how easy it is to use the """ syntax to create mutliline strings. Notice, also, how a ' is automatically escaped.


Listing 34. Multiline strings in Nice
var poem = """ 
   This is a multiline String.
   Why wouldn't anyone want to make one?
   """;

println(poem);

var line1 = "All roads lead to where you are";
var line2 = "Love don't need to find a way";
var concat = "Da da " line1 " da da " line2;

println(concat);

println("concat " line1 " with " line2);


Nice also makes string concatenation a breeze by relaxing the Java language's normal + syntax. As a result, you can call println() and drop the associated concatenations, as shown above in Listing 34.

Conclusion

Many of the nicest features demonstrated in this month's installment of alt.lang.jre are also available in the 5.0 release of the Java language. Not everyone can immediately jump on the Java 5 bandwagon, however, and for the rest of us, there's Nice. In addition to letting you play with parametric classes, multimethods, design by contract, and numerous other convenience features on practically any version of the JVM, Nice gives you a gentle introduction to the benefits of expressiveness and agility on any development platform. As demonstrated here, Nice is a particularly sound choice when it comes to crafting safer, more modular code on the Java platform. See the Resources section to learn more about Nice, and stay tuned for next month's installment of alt.lang.jre, which will provide an introduction to Rhino.



Resources



About the author

Andrew Glover is the President of Stelligent Incorporated, a Washington, DC, metro area company specializing in the construction of automated testing frameworks, which lower software bug counts, reduce integration and testing times, and improve overall code stability.




Rate this page


Please take a moment to complete this form to help us better serve you.



 


 


Not
useful
Extremely
useful
 


Share this....

digg Digg this story del.icio.us del.icio.us Slashdot Slashdot it!



Back to top