The basics (tutorial)

jellicle edited this page Aug 8, 2011 · 1 revision

Table of Contents

Introduction

This tutorial describes the basics of programming in Nemerle. It is also a quick tour of some of the features and programming concepts that distinguish Nemerle from other languages.

We assume the reader is familiar with C#, Java or C++. But even if you are new to programming, you should find the code easy to understand.

Nemerle, .NET, and Mono

Nemerle is a .NET compatible language. As such, it relies heavily on the .NET Framework, which not only defines how critical parts of all .NET languages work, but also provides these services:

  • A runtime environment, called the Common Language Runtime (CLR), which is shared among all .NET languages. This is similar to the Java VM
  • A set of libraries, called the Base Class Libraries (BCL), which contain thousands of functions that perform a wide range of services required by programs
The BCL and CLR ensure that programs written in any .NET language can be easily used in any other .NET language. These features of language neutrality and interoperability make .NET an attractive platform for development.

Further, Nemerle is compatible with Mono, an open-source implementation of the published .NET Common Language Infrastructure (CLI) standard. This opens up the exciting possibility of writing .NET programs that not only work across different languages, but span different operating systems. With Mono, you can design programs that run on Linux, Mac OS X, and the BSD Unix flavors, as well as Windows.

So, while Nemerle is defined (and constrained) in part by the .NET Framework, it is very much its own language. It offers a unique combination of features not found in other .NET languages, which give it advantages of conciseness and flexibility that should prove attractive to a wide range of programmers, from students to seasoned developers.

Getting Started

To run the examples in this tutorial, you will need to install Nemerle. Hacker types will want to download the source.

Simple Examples

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

Hello World

We start with a classic first example:

System.Console.WriteLine ("Hello world!");

To run this example:

  • Write it with your favorite text editor and save it as hello.n
  • Get to the console by running cmd in Windows, or the terminal window in Linux/BSD
  • Run the Nemerle compiler by typing ncc hello.n
  • The output goes to out.exe
  • Run it by typing out or mono out.exe depending on your OS
Observe how an individual code statement ends with a semi-colon;

This program writes "Hello world!" to the console. It does this by calling System.Console.WriteLine, a function in the .NET Framework.

As this example shows, you can write a bunch of statements in a file (separated by semi-colons), and Nemerle will execute them. However, this example really isn't a proper, stand-alone program. To make it one, you need to wrap it in a class.

Classes: a First Look

Lets expand our example to include a class. Enter these lines in your hello.n file:

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

Notice how blocks of code are grouped together using curly braces { }, typical of C-style programs.

When you compile and run this program, you get the same results as before. So why the extra lines? The answer is that Nemerle, like most .NET languages, is object-oriented:

  • class Hello simply means that you are defining a class, or type of object, named Hello. A class is a template for making objects. Classes can be used standalone, too, as is the case here.
  • static Main defines a function Main, which belongs to class Hello. A function that belongs to a class is called a method of the class. So, here you would say "function Main is a method of class Hello."
  • By convention, program execution starts at static Main. The keyword static means the method can be called directly, without first creating an object of type Hello. Static methods are the equivalent of public or module-level functions in non-object languages.
This example is much closer to what a C# programmer would write. The only difference is that in Nemerle we write the method's return type on the right, after the colon. So, static Main():void specifies that method Main returns void, or no usable type. This is the equivalent of a subroutine in Basic.

The Adder

Adder is a very simple program that reads and adds two numbers. We will refine this program by introducing several Nemerle concepts.

To start, enter and compile this code:

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

 using System;

 //  This is also a comment
 
 public class Adder      //  As in C#, we can mark classes public.
 {
   public static Main () : void         //  Methods, 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 ()));
   }
 }

When run, Adder lets you type in two numbers from the console, then prints out the sum.

As you can see, a lengthy statement can be continued on multiple lines, and mixed with comments, as long as it ends with a semi-colon;

The using declaration imports identifiers from the specified namespace, so they can be used without a prefix. This improves readability and saves typing. Unlike C#, Nemerle 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 ()));
   }
 }

You probably noticed that the code that reads and converts the integers is needlessly duplicated. We can simplify and clarify this code by factoring it into its own method:

 using System;

 public class Adder
 {
   //  Methods are private by default.
   static ReadInteger () : int
   {
     Int32.Parse (Console.ReadLine ())
   }

   public static Main () : void
   {
     def x = ReadInteger (); //  Value definition.
     def y = ReadInteger ();
     //  Use standard .NET function for formatting output.
     Console.WriteLine ("{0} + {1} = {2}", x, y, x + y);
   }
 }

Within the Main method we have defined two values, x and y. This is done using the def keyword. Note that we do not write the value type when it is defined. The compiler sees that ReadInteger returns an int, so therefore the type of x must also be int. This is called type inference.

There is more to def than just declaring values: it also has an impact on how the value can be used, as we shall see in the next section.

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 def will save typing, because in most cases type names are far longer:

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

When creating objects, Nemerle does not use the new keyword. This aligns nicely with the .NET concept that all types, even simple ones like int and bool, are objects. That being said, simple types are a special kind of object, and are treated differently during execution than regular objects.

Counting Lines in a 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);
   }
 }
Several things about this example require further remarks. The first is the very important difference between the lines marked (1) and (2).

In line (1) we define an immutable variable, sr. Immutable means the value cannot be changed once it is defined. The def statement is used to mark this intent. This concept may at first seem odd, but quite often you will find the need for variables that don't change over their lifetime.

In (2) we define a mutable variable, line_no. Mutable values are allowed to change freely, and are defined using the mutable statement. This is the Nemerle equivalent of a variable in C#. All variables, mutable or not, have to be initialized before use.

In (3) we see a while loop. While the line is not null (end of file), this loop writes the line to the console, counts it, and reads the next. It works much like it would in C#. Nemerle also has do ... while loops.

We see our mutable counter getting incremented in (4). The assignment operator in Nemerle is =, and is similar to C#.

Lastly, in (5), we come to the end of of our while loop code block. The line count gets written after the while loop exits.

Functional examples

This section introduces some of the more functional features of Nemerle. We will use the functional style to write some simple programs, that could easily be written in the more familiar imperative style, to introduce a few concepts of the functional approach.

Functional Programming: a First Look

Functional programming (FP) is a style in which you do not modify the state of the machine with instructions, but rather evaluate functions yielding newer and newer values. That is, the entire program is just one big expression. In purely functional languages (Haskell being the 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. A recursive function calls itself repeatedly until some end condition is met, at which time it returns its result.

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

We will be using the terms method and function interchangeably.

Rewriting Line Counter without the loop

Let's rewrite our previous Line Counter example using a recursive function instead of the loop. It will get longer, but that will get fixed 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 a nested method called read_lines. This method simulates the while loop used in our previous example. It takes no parameters and returns a 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 not. When a function body ends with call to another function no new stack frame is created. It is called a tail call. Thanks to it the example above is as efficient as the while loop we saw 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 the condition negated.

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 a nested method called read_lines. However this time it takes one integer parameter -- the current line number. It returns the number of lines in the 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 a method is its last expression.

(4) Otherwise (it was not last line) we write the line we just read. Next (5) we call ourselves to read the next line. We need to increase the line number, since it is the next line that 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.

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 quite hard to tell the type of parameter, from just looking how it is used. For example consider:

 class HarderInference
 {
   static Main () : int {
     def f (x) {
       x.Length
     };
     f ("foo");
   }
 }

When compiling the f method we cannot tell if x is a string or array or something else. Nevertheless, we can tell it later (looking at the invocation of f) and Nemerle type inference engine does it.

If a function with incomplete type information is not used or its type is ambiguous, the compiler will refuse to compile it.

More info

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

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.