- Notifications
You must be signed in to change notification settings - Fork91
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 withMono, an open-source implementation of the published .NETCommon 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 toinstall Nemerle. Hacker types will want todownload 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
outormono out.exedepending on your OS
;This program writes "Hello world!" to the console. It does this by callingSystem.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 yourhello.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 Hellosimply means that you are defining aclass, 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 Maindefines a functionMain, which belongs to classHello. A function that belongs to a class is called amethod of the class. So, here you would say "functionMainis a method of classHello."- By convention, program execution starts at
static Main. The keywordstaticmeans 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 methodMain returnsvoid, 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 commentpublicclass Adder// As in C#, we can mark classes public. {publicstatic 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;
Theusing 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;publicclass Adder {publicstatic 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;publicclass Adder {// Methods are private by default.static ReadInteger () :int { Int32.Parse (Console.ReadLine ()) }publicstatic 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 theMain method we have defined twovalues,x andy. This is done using thedef keyword. Note that we do not write the value type when it is defined. The compiler sees thatReadInteger returns anint, so therefore the type ofx must also beint. This is calledtype inference.
There is more todef 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 usingdef instead ofint as you would do in C# (both are 3 characters long :-). Howeverdef 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 thenew keyword. This aligns nicely with the .NET concept that all types, even simple ones likeint andbool, are objects. That being said, simple types are a special kind of object, and are treated differently during execution than regular objects.
class LineCounter {publicstatic 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 animmutable variable,sr. Immutable means the value cannot be changed once it is defined. Thedef 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 amutable variable,line_no. Mutable values are allowed to change freely, and are defined using themutable 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 awhile 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 hasdo ... 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 stateof the machine with instructions, but rather evaluate functions yieldingnewer and newer values. That is, the entire program is just one big expression. Inpurely functional languages (Haskell being the main example) you cannot modifyany objects once they are created (there is no assignment operator, like = in Nemerle). There are no loops, justrecursive 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 wheneveryou find it necessary. Some algorithms have a very natural representationwhen written in functional style -- for example functional languagesare 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 termsmethod andfunction 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 {publicstatic 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 anested method calledread_lines. This method simulates thewhile loop used in our previous example. It takes no parameters and returns a void value.
(2) If line wasn'tnull (i.e. it was not the last line), (3) we writethe line we just read, (4) increase the line number, and finally (5) callourself to read rest of the lines. The when expression is explained below.
Next (6) we callread_lines for the first time, and finally (7) printthe line count.
Theread_lines will get called as many times as there arelines in the file. As you can see this is the same as thewhile loop,just expressed in a slightly different way. It is very important togrok this concept of writing loops as recursion, in order to programfunctionally 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 atail call. Thanksto it the example above is as efficient as thewhile loop wesaw before.
In Nemerle the if expression always need to have the else clause. It'sdone this way to avoid stupid bugs with dangling-else:
// C#, misleading indentation hides real code meaningif(foo)if(bar)m1();elsem2();
If you do not want theelse clause, usewhen expression,as seen in the example. There is alsounless expression, equivalent towhen with the condition negated.
Our previous aim of rewriting line counter removed the loop and onemutable value. However one mutable value has left, so we cannot say theexample is written functionally. We will now kill it.
class FunctionalLineCounter {publicstatic 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 calledread_lines. Howeverthis time it takes one integer parameter -- the current line number. Itreturns the number of lines in the entire file.
(2) If line we just read is null (that was last line), we (3) returnthe current line number as number of lines in entire file. As you canseethere is no return statement. The return value of a method is itslast 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 ofread_lines we return what the next invocation ofread_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 ofread_lines.
In (6) we call theread_lines nested method, with initial linenumber of 0 to read the file and print out line count.
We have already seen type inference used to guess types of valuesdefined withdef ormutable. It can be also used toguess 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 arerequired in top-level methods (that is methods defined in classes, notin other methods). This is design decision, that is here not to changeexternal interfaces by accident.
It is sometimes quite hard to tell the type of parameter, from justlooking 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 arrayor 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 typeis ambiguous, the compiler will refuse to compile it.
Now, once you read through all this, please move to theGrokking Nemerletutorial, that is much more complete. You can also have a look atThe Reference Manual if you are tough.