Don't Panic! - Nemerle Basics Explained

1. Introduction

This tutorial will describe basics of programming in Nemerle. We assume the reader is familiar with C#, Java or C++.

2. Simple examples

This section lists simple example that look almost the same as in C# (or Java, or C++).

2.1. The very-first example

class Hello
{
  static Main () : void
  {
    System.Console.WriteLine ("Hello world!");
  }
}

As you can see the only difference between Nemerle and C# version of this program is that we write method's return type at the right, after colon. There are some reasons to do it this way, we will explain them later.

To run this example:

For people unfamiliar with C#: the entry point of execution is the static Main method in some class. System.Console.WriteLine is a call to a function from the .NET framework.

2.2. The adder

We will now write a very simple program to read and add two numbers.

/* Our second example.  This is a comment. */
// This is also a comment.

using System;

public class Adder      // As in C# we can mark class public.
{
  public static Main () : void          // And method too.
  {
    /* Read two lines, convert them to integers and return their
       sum.  */
    Console.WriteLine ("The sum is {0}", 
                       // System.Int32.Parse converts string into integer.
                       Int32.Parse (Console.ReadLine ()) + 
                       Int32.Parse (Console.ReadLine ()));
  }
}

The using declaration imports identifiers from specified namespace, so they can be used without prefix. Unlike in C# it can also import members from classes, not only from namespaces. For example:

using System;
using System.Console;

public class Adder
{
  public static Main () : void
  {
    WriteLine ("The sum is {0}", 
               Int32.Parse (ReadLine ()) + 
               Int32.Parse (ReadLine ()));
  }
}

As you can see for both lines we first read them and then convert to integers. We can factor this into a method:

using System;

public class Adder
{
  // It is private by default.
  static ReadInteger () : int
  {
    Int32.Parse (Console.ReadLine ())
  }
  
  public static Main () : void
  {
    def x = ReadInteger (); // Variable definition.
    def y = ReadInteger ();
    // Use standard .NET function for formatting output.
    Console.WriteLine ("{0} + {1} = {2}", x, y, x + y);
  }
}

The method definition looks the way we would expect from Main example. However in Main itself we define two values: x and y. This is done using the def keyword. As you can see we do not write type of variable at the place of definition. The compiler sees that ReadInteger returns int and therefore type of x has to also be int. This is called type inference.

In this example we see no gain from using def instead of int as you would do in C# (both are 3 characters long :-). However in most cases type names are far longer:

FooBarQuxxFactory fact = new FooBarQuxxFactory (); // C#
def fact = FooBarQuxxFactory (); // Nemerle

As you can see we also do not use the new keyword.

2.3. Counting lines in file

class LineCounter
{
  public static Main () : void
  {
    // Open a file.
    def sr = System.IO.StreamReader ("SomeFile.txt");   // (1)
    mutable line_no = 0;                               // (2)
    mutable line = sr.ReadLine ();                     
    while (line != null) {              // (3)
      System.Console.WriteLine (line);
      line_no = line_no + 1;            // (4)
      line = sr.ReadLine ();
    };                                  // (5)
    System.Console.WriteLine ("Line count: {0}", line_no);
  }
}

There are few remarkable things about this example. First one is the very important difference between lines marked (1) and (2).

In (1) we define immutable value sr. Immutable means it cannot be changed once it is defined. This may first seem odd, but it is quite often that variables are never changed, once created. The def statement is there to mark this intent.

In (2) we define mutable value. This is the same thing as variable in C#, It has to be initialized before use and can be changed later. The assignment operator in Nemerle is called =, it works much like in C#. We see its use in (4).

In (3) we see the while loop. It works much like in C#. We also have do ... while loops in Nemerle.

3. Functional examples

This section will introduce some more functional features of Nemerle.

Functional programming (FP) is style in which you do not modify state of the machine with instructions, but rather evaluate functions yielding new and new values. That is entire program is just one big expression. In purely functional language (Haskell being main example) you cannot modify any objects once they are created (there is no assignment operator, like = in Nemerle). There are no loops, just recursive functions.

Nemerle does not force you to use FP. However you can use it whenever you find it necessary. Some algorithms have very natural representation when written in functional style -- for example functional languages are very good at manipulation of tree-like data structures (like XML, in fact XSLT can be thought of as a functional language).

However at the beginning we will use functional style to write simple programs, that could be written in imperative style without much problems.

We will be using terms method and function interchangeably.

3.1. Rewriting line counter without the loop

We will now rewrite our previous example not to use loops, just a recursive function. It will get longer, but we will fix that soon.

class LineCounterWithoutLoop
{
  public static Main () : void
  {
    def sr = System.IO.StreamReader ("SomeFile.txt");
    mutable line_no = 0;

    def read_lines () : void {            // (1)
      def line = sr.ReadLine ();
      when (line != null) {               // (2)
        System.Console.WriteLine (line);  // (3)
        line_no = line_no + 1;            // (4)
        read_lines ()                     // (5)
      }
    };
    
    read_lines ();      // (6)
    
    System.Console.WriteLine ("Line count: {0}", line_no); // (7)
  }
}

In (1) we define nested method called read_lines inside the Main method. The method is there to simulate the while loop from our previous example. It takes no parameters and returns void value.

(2) If line wasn't null (i.e. it was not the last line), (3) we write the line we just read, (4) increase the line number, and finally (5) call ourself to read rest of the lines. The when expression is explained below.

Next (6) we call read_lines for the first time, and finally (7) print the line count.

The read_lines will get called as many times as there are lines in the file. As you can see this is the same as the while loop, just expressed in a slightly different way. It is very important to grok this concept of writing loops as recursion, in order to program functionally in Nemerle.

If you are concerned about performance of this form of writing loops -- fear you not. When function body ends with call to another function -- no new stack frame is created. It is called tail call. Thanks to it the example above is as efficient as the while loop we seen before.

In Nemerle the if expression always need to have the else clause. It's done this way to avoid stupid bugs with dangling-else:

// C#, misleading indentation hides real code meaning
if (foo)
   if (bar)
     m1 ();
else
   m2 ();

If you do not want the else clause, use when expression, as seen in the example. There is also unless expression, equivalent to when with condition negated.

3.2. Rewriting line counter without mutable values

Our previous aim of rewriting line counter removed the loop and one mutable value. However one mutable value has left, so we cannot say the example is written functionally. We will now kill it.

class FunctionalLineCounter
{
  public static Main () : void
  {
    def sr = System.IO.StreamReader ("SomeFile.txt");
    def read_lines (line_no : int) : int {   // (1)
      def line = sr.ReadLine ();
      if (line == null)      // (2)
        line_no              // (3)
      else {
        System.Console.WriteLine (line);  // (4)
        read_lines (line_no + 1)          // (5)
      }
    };
    
    System.Console.WriteLine ("Line count: {0}", read_lines (0)); // (6)
  }
}

In (1) we again define nested method called read_lines. However this time it takes one integer parameter -- the current line number. It returns number of lines in entire file.

(2) If line we just read is null (that was last line), we (3) return the current line number as number of lines in entire file. As you can see there is no return statement. The return value of method is its last expression.

(4) Otherwise (it was not last line) we write the line we just read. Next (5) we call ourself to read the next line. We need to increase line number, since it is next line what we will be reading. Note that as a return value from this invocation of read_lines we return what the next invocation of read_lines returned. It in turn returns what the next invocation returned and so on, until, at the end of file, we reach (3), and final line count is returned through each invocation of read_lines.

In (6) we call the read_lines nested method, with initial line number of 0 to read the file and print out line count.

3.3. Type inference

We have already seen type inference used to guess types of values defined with def or mutable. It can be also used to guess type of function parameters and return type. Try removing the : int constraints from line marked (1) in our previous example.

Type inference only works for nested functions. Type annotations are required in top-level methods (that is methods defined in classes, not in other methods). This is design decision, that is here not to change external interfaces by accident.

It is sometimes impossible to tell the type of parameter, from just looking how it is used. For example consider:

class FailedInference 
{
  Main () : void {
    def f (x) { // (1)
      x.Length  // (2) ncc reports error here
    }; 
    f ("foo");
  }
}

When compiling the f method we cannot tell if x is a string or array or something else. We could tell it later (looking at f invocation), but we decided that functions need to be typeable by just looking at the definition. This allows for far less confusing error messages.

To fix this program add : string constrain on x. You need not constrain return type of f. That is change line marked (1) to def f (x : string) {.

4. More info

Now, once you read through all this, please move to Grokking Nemerle tutorial, that is much more complete. You can also have a look at The Reference Manual if you are tough.