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 powerfull 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 define 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).
 

Defining methods

Method definitions are place outside of classes. Their order does not matter at all. The definitions of a single method may even occur in several files (this is not a flaw, it's an important feature that allows modularity).

So after the two above class definitions, we define to alternatives for method display :

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

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

Parametric classes

Classes and interfaces can have type parameters. For example, the Collection interface is parametrized 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:
<Any 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 laters 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:
<Any T, Any U> boolean equals(T, U);

A more sensible example is the pair creation function:
class Pair<T, U> { ... }

<Any T, Any U> Pair<T, U> pair(T, U);
 

Multiple dispatch

In Nice, the choice of the method alternative is made at run-time, based on all parameters (in java, only the first, implicit parameter is used to choose the alternative).

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

Java
Nice
class Object
{
  boolean equals(Object that)
  {
    return this == that;
  }
}

class Person extends Object 
{
  String name;
  int age;

  boolean equals(Object that)
  {
    if(!(that instanceof Person))
      return false;
    return name.equals(((Person) that).name)
       && age==((Person) that).age;
  }
}
<Any T> boolean equals(T, T);

equals(o1, o2) = o1==o2;

class Person
{
  String name;
  int age;
}

equals(p1@Person, p2@Person) =
  p1.name.equals(p2.name) &&
  p1.age==p2.age;
In the Nice version, the last equals alternative will be executed when both parameters are instance 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 sort of automatically done by the compiler for you. The code looks cleaner, 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, and it becomes extremely difficult in large projects).

Precise types

Java's type system is too simple to express many usefull types. For instance, let's suppose we want to declare the method map on Collections. Map applies a function to each element of the collection, and returns a new collection holding the results. That is:
map(f, [e1, ..., en]) = [f(e1), ..., f(en)];
 
 
Java
Nice
interface BooleanFunction
{
  boolean apply(Object);
}

interface Collection
{
  Collection filter(booleanFunction f);
}

interface List extends Collection 
{ ... }

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

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

main(args)
{
  List<int> l1;
  fun(int)(boolean) f;
  ...
  List 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 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();
}
<Any T> T clone(T);

main(args)
{
  java.util.Date d1;
  ...
  java.util.Date d2 = d1.clone();
}

Instructions and expressions

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

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 evergrowing set of Java libraries.

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

import java.io.*;

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

However, one might want to explicitely import methods to give them a more precise type (to benefit from parametric types for instance). Here is a more sophisticated example for the java.util.Map interface:

interface Map<Key,Element> = native java.util.Map;

<Any Key, Any Element> Element get(Map<Key,Element>, Key) =
  native Object java.util.Map.get(Object);
 

Calling Nice code from Java is also possible, but is not so straightforward because the generated methods get mangled names, and because of the different dispatch mecanism. This could be worked on if necessary.
 

What else?

Many features are missing in this tutorial: abstract interfaces, general constraints, final implementation, ...

General information about Nice : the Nice home page