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.
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).
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;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)];
In the nice version, the alike keyword is a type that means "the same type as the implicit receiver argument".
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); }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
- No casts. We claim that our powerfull 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 explicitely write what happends 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. This is future work to handle them.
Additional constructs
- Closures (functions written inside expressions). The expression fun(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 fun(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 concrete syntax for functional type (T1, T2, ..., Tn) -> T is fun(T1, T2, ..., Tn)(T).
- Functional-like syntax for method definitions. One can write f(x@P) = e; as an equivalent form for f(x@p) { 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). It's 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);
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