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
- No casts. We claim that our powerful type system makes it possible to avoid nearly every cast used in other object-oriented languages (say 95% of them!). For the 5% remaining, it is possible to write methods that do the same job, except you have to explicitly write what happens if the "cast" fails, which is a nice property.
- Visibility modifiers (public, private) are accepted for class members, but are currently ignored. There is no theoretical problem to add visibility modifiers. We are planning to support them before version 1.0.
Additional constructs
- Closures (functions written inside expressions). The expression (T1 p1, T2 p2)=>e is the two parameters function that returns e. The return type of the function is computed, so you don't have to write it. Parameters p1 and p2 can of course occur in e. Outside variables can also safely occur in e, in which case their reference is captured (not their value). An alternate syntax is (T1 p1, T2 p2)=>{ inst1; ...; instn; }. Every execution of the body should lead to a return statement. The return type of the function is the lowest common type of the returned expressions.
- Functional arguments. A function or a method can have functional arguments. The syntax for functional types is (T1, T2, ..., Tn) -> T. The parentheses can be omitted if there is only one argument type: int->int.
- Functional-like syntax for method implementations. One can write f(P x) = e; as an equivalent form for f(P x) { return e; }. This is very handy when defining methods that return simple values, depending on the type of the argument -- that is doing pattern-matching.
- Tuples. Tuples allow for grouping several values in a single expression, without the pain of creating a specific class. For instance, ("String", 2*3, x.toString()) is a tuple (a triplet in that case). Its type is written (String, int, String). Tuple types are covariant, so a couple of int can be used where a couple of long is expected. A funny feature is that swapping two variables can be done without a temporary variable, using tuples: (x, y) = (y, x).
- Named and optional parameters. If a method is declared with named parameters, it is possible to specify them at the call site, which makes it unnecessary to remember the order of the parameters, and makes the call clearer. For instance:
void copy(File from, File to);
...
copy(from: f1, to: f2);
copy(to: f3, from: f4);It is possible to omit the names at the call site, in which case the usual declaration order is used:
copy(f1, f2); // from f1 to f2Additionally, named parameters can be given a default value, in which case their presence is optional at the call site.
void copy(File from, File to, int bufferSize = 1000);
...
copy(from: f1, to: f2); // with a buffer size of 1000
copy(from: f1, to: f2, bufferSize: 4000);
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