-
Notifications
You must be signed in to change notification settings - Fork 89
The basics (tutorial)
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 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
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.
To run the examples in this tutorial, you will need to install Nemerle. Hacker types will want to download the source.
This section lists simple examples that look almost the same in C# (or Java, or C++).
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
ormono out.exe
depending on your OS
;
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.
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, namedHello
. A class is a template for making objects. Classes can be used standalone, too, as is the case here. -
static Main
defines a functionMain
, which belongs to classHello
. A function that belongs to a class is called a method of the class. So, here you would say "functionMain
is a method of classHello
." - By convention, program execution starts at
static Main
. The keywordstatic
means the method can be called directly, without first creating an object of typeHello
. Static methods are the equivalent of public or module-level functions in non-object languages.
static Main():void
specifies that method Main
returns void
, or no usable type. This is the equivalent of a subroutine in Basic.
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.
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);
}
}
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.
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 (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.
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.
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.
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.
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.