-
Notifications
You must be signed in to change notification settings - Fork 89
Grok The rest
This page is a part of the Grokking Nemerle tutorial.
This feature (in this form) comes from python. When you call a function you can name some of its parameters (which allows putting them in a different order than in the definition).
frobnicate (foo : int, do_qux : bool,
do_baz : bool,
do_bar : bool) : int
{
// this is completely meaningless
if (do_qux && !do_baz) foo * 2
else if (do_bar) foo * 7
else if (do_baz) foo * 13
else 42
}
Main () : void
{
// Parameters' names can be omitted.
def res1 = frobnicate (7, true, false, true);
// This is the intended usage -- the first
// (main) parameter comes without a name
// and the following flags with names
def res2 = frobnicate (7, do_qux = true,
do_baz = false,
do_bar = true);
// You can however name every parameter:
def res3 = frobnicate (foo = 7, do_qux = true,
do_baz = false, do_bar = true);
// And permute them:
def res3 = frobnicate (do_qux = true, do_bar = true,
do_baz = false, foo = 7);
// You can also omit names for any number of leading
// parameters and permute the trailing ones.
def res2 = frobnicate (7, true,
do_bar = true,
do_baz = false);
()
}
The rules behind named parameters are simple:
- named parameters must come after all unnamed (positional) parameters
- positional parameters are assigned first
- the remaining parameters are assigned based on their names
- parameters with default values will be assigned automatically (except those that are already assigned)
The void literal is written: ().
The void literal is quite a tricky thing, since it represents the
only value of type void
, which, judging from the name,
should be, errr... void. In fact, in some other functional languages
this type is called unit
, but in Nemerle the name comes from
System.Void
and the void
type is an alias for it.
In C# the void
type is used as a return type of functions. It
does not make much sense to use it elsewhere. It could be needed, however,
if, for example, you want to use the Hashtable [a, b]
type as
a set representation of strings. You can use Hashtable [string,
void]
then. And this is the place to use the void value -- when
you call a set method, you need to pass something as a value to set --
and you pass the void value.
You can also use the void value to return it from a function -- as you
remember, the return value of function is the last expression in its
body. However, in most cases the last expression will already have the
right void
type.
You can overload existing operators or add new ones simply by specifying
them as a static method in some type. The name of method should be
some quoted (with @
) operator. For example, the following
class definition:
class Operand {
public val : int;
public this (v : int) { val = v }
public static @<-< (x : Operand, y : Operand) : Operand {
Operand (x.val + y.val);
}
}
contains the <-<
binary operator processing Operand
objects. It can be used like:
def x = Operand (2);
def y = Operand (3);
def z = x <-< y;
assert (z.val == 5);
Unary operators can be created by giving only one parameter to the method.
Blocks are a generic, structured replacement for the imperative
break
and continue
statements. Consider the
following example of prematurely return
ing from a function
in C#:
bool AllGreaterThan(int[] numbers, int limit) {
foreach (int number in numbers) {
if (number <= limit)
return false;
}
return true;
}
This works in C#, because the first return
statement aborts
execution of the entire function. In Nemerle, we have a similar
control structure that is independent of functions:
AllGreaterThan: {
foreach (number in numbers) {
when (number <= limit)
AllGreaterThan(false);
}
true;
}
This looks similar to the C# example, but we are using the name of
the block, AllGreaterThan
, instead of return
, to
exit from the named block, instead of the entire function, passing
the given value.
The Blocks article provides a more detailed description of blocks
and their use case, and shows how to get C#-like continue
,
break
and continue
statements in Nemerle.
Nemerle has very powerful code-generating macros. They are more akin to Lisp macros than macros found in C preprocessor. We are not going to explain here how to write macros (if you are curious, please see macros tutorial), but will describe a few often used macros.
First of all, if
, while
, for
, foreach
,
when
, using
, lock
, etc. are all macros.
When you write code like:
using (stream = System.IO.FileStream ("file.txt"))
{
lock (this) {
...
}
}
it first gets transformed into
def stream = System.IO.FileStream ("file.txt", System.IO.FileMode.Open);
try {
System.Threading.Monitor.Enter (this);
try {
...
} finally {
System.Threading.Monitor.Exit (this)
}
} finally {
def disp = (stream : System.IDisposable);
when (disp != null) {
disp.Dispose ()
}
}
The transformation is done by macros, which also introduce using and lock syntax to the language.
The greatest thing is that programmer is allowed to define his own macros, introducing his own syntax.
Design by contract macros allows you to specify assertions about your program. In other languages they are usually laying around in comments, while in Nemerle you can explicitly decorate code. This way contracts are instantly checked during program execution.
Other examples of macros:
-
printf
,sprintf
-- uses a similar syntax to the same function in C, but is checked at the compile time -
scanf
-- likewise -
print
-- it does the $-expansion known from shell or perl -
assert
-- much like the C macro