The Nice programming language

This page is intended to provide a gentle introduction to the Nice programming language. The goal is to help you write your first Nice programs. It does not describe every feature of the language, nor gives it a complete description of the powerful type system.
 

Requirements

For simplicity and conciseness, this tutorial presents Nice as an extension of the Java programming language. You should therefore be familiar with Java. If not, you could read about it, for example Javasoft's tutorial.
 

Declaring classes and methods

Classes and methods can be declared as in Java:
class Person 
{ 
  String name; 
  int age; 
  
  String display(); 
} 

class Worker extends Person 
{ 
  int salary; 
} 

Note that String display(); declares a method, that is informs that this method exists. Now we have to implement it, that is tell what code is to be executed, depending on the runtime type of the person (in this short example either Person or Worker).
 

Implementing methods

Method implementations can be placed outside of classes. Their order does not matter. The implementations of a single method may even occur in several files (this is an important feature that allows modularity).

So after the two above class definitions, we write two implementations for method display:

display(Person p) 
{ 
  return p.name + " (age=" + p.age + ")"; 
} 

display(Worker p) 
{ 
  return p.name + " (age+" + p.age + ", salary=" + p.salary + ")"; 
} 

 

Multiple dispatch

In Nice, the choice of the method implementation is made at run-time, based on all parameters (in java, only the implicit parameter this is used to choose the alternative). Such methods are thus called multi-methods.

Let's take the example of the equals method, that tests if any two objects are equal.
 
 

Java
Nice
class Person
{
  String name;
  int age;

  boolean equals(Object that)
  {
    if(!(that instanceof Person))
      return false;
    return 
       name.equals(((Person) that).name)
       && age==((Person) that).age;
  }
}
class Person
{
  String name;
  int age;
}

equals(Person p1, Person p2) =
  p1.name.equals(p2.name) &&
  p1.age==p2.age;

In the Nice version, this implementation of equals will be executed when both parameters are instances of class Person. So the type of the second argument is also known, and no manual instanceof and no cast are necessary (red parts of the java code). This job is automatically done by the compiler for you. The code looks cleaner, it is simpler to understand, and it is automatically guaranteed that no runtime exception will occur (this simple java code would not break either, but you have to think about it to get this confidence, and it becomes extremely difficult in large projects).

Another great advantage of multi-methods is that they offer an attractive alternative to the Visitor Pattern. This solution is presented in a full example.

There is a more detailed introduction to methods in Nice on the Wiki.
 
 

Parametric classes

Classes and interfaces can have type parameters. For example, the Collection interface is parameterized by the type of its elements:

interface Collection<T> 
{ 
  ... 
} 

If a class (resp. interface) has type parameters, then all its sub-classes (resp. sub-interfaces and implementing classes) must have the same type parameters.

class LinkedList<T> implements Collection<T> 
{ 
  T head;
  LinkedList<T> tail; 
} 

A consequence of this rule is that there is no class (like Object in Java) that is an ancestor of all classes. There could not be, since all classes do not have the same number of type parameters.
However, it is possible to express that a method takes arguments of any type. For instance the equals method is declared in Nice:

<T> boolean equals(T, T); 

One can read this as "for Any type T, the method equals takes two objects of type T, and returns a boolean".

Thanks to the usual subsumption rule, this type makes it possible to call equals with arguments of different type, as long as they are compatible (have a common super type). For instance, it's legal to use equals to compare expressions of type Collection<int> and LinkedList<int>, while it is not with types Collection<String> and String.

This approach is more sensible than Java's one, where the latter would be allowed and would always return false (not even raising a runtime error), while it is very likely a bug to compare a collection of strings to a string.

Note that it is also possible to define a function that takes two unrelated and unconstrained types. So it would be possible to define equals to have the same typing behaviour it has in Java:

<T, U> boolean equals(T, U);

Precise types

Java's type system is too simple to express many useful types. For instance, let's suppose we want to declare the method filter on collections. This method applies a function to each element of the collection, and returns a new collection containing the elements for which that function returns true. Furthermore, the new collection is an instance of the same class as the original collection: filter applied to a List return a new List, filter applied to a Vector return a new Vector, ...
 
 
Java
Nice
interface BoolFunction
{
  boolean apply(Object);
}

interface Collection
{
  Collection filter(BoolFunction f);
}

interface List extends Collection
{ ... }

static void main(String[] args)
{
  List l1;
  BoolFunction f;
  ...
  List l2 = (List) l1.filter(f);
}
interface Collection<T>
{
  alike<T> filter(T->boolean f);
}

interface List<T>
extends Collection<T> 
{ ... }

void main(String[] args)
{
  List<int> l1;
  int->boolean f;
  ...
  List<int> l2 = l1.filter(f);
}

In the Nice version, the alike keyword is a type that means "the same type as the implicit receiver argument".

Note that there is not special treatment for the alike construct in the core type system. alike is just syntactic sugar for a more general concept: polymorphic constrained types.

The clone example might be more familiar to Java users. In Java, the clone method, defined in class Object, has a Object return type. In fact, one would like to express that the clone method returns an object of the same type as its argument. This is possible in Nice, and it allows for cast-less use of clone.
 

Java
Nice
class Object
{
  Object clone();
  ...
}

void main(String[] args)
{
  java.util.Date d1;
  ...
  java.util.Date d2 = (Date) d1.clone();
}
<T> T clone(T);

void main(String[] args)
{
  java.util.Date d1;
  ...
  java.util.Date d2 = d1.clone();
}

 

Instructions and expressions

The code of a method implementation, that goes between { and }, can be almost any legal Java code.
Here is a list of the differences:
Missing constructs
Additional constructs

The main function

The main function is the start of any program. It is a normal function, that takes as its parameters the array containing the arguments of the program.
void main(String[] args)
{
  System.out.println("Hello, world!");
}

Interfacing with Java

It is possible to use java classes and methods from Nice programs. This is a great advantage, since it gives access to the gigantic and ever-growing set of Java libraries.

One can just use java classes in types, and call java methods in Nice code. It is not necessary to explicitly import classes or methods. An import statement can be used if one does not want to repeat a package name.

import java.io.*;

{
  String s;
  ...
  java.io.Writer w = new FileWriter(s);
  w.write("Hello world");
  w.close();
}
 

More advanced usage is described in the corresponding section of the User's manual: giving more precise types to Java methods, calling Nice code from a Java program, ...
 

To go further

Chech this list of documents to get started with Nice. For a complete description of the language, see the User's manual.

General information about Nice : the Nice home page