-
Notifications
You must be signed in to change notification settings - Fork 89
Nemerle for OOP Programmers Week 5
This is the last lesson in this course. It discusses topics mostly related to object-oriented programming in Nemerle. This should be fairly easy for C# programmers, while people with Java/C++ background will have a slightly harder time.
This lesson again borrows topics from Grokking Nemerle.
In Nemerle, types can live in namespaces. If type X is defined in
namespace Y, then its full name is Y.X. It is a good idea to put
all your code in some unique namespace if you're going to distribute it,
to avoid name clashes. All classes in the Base Class Library live in some
namespace (mostly in System
and its sub-namespaces).
The syntax for defining namespaces is:
namespace FooBarCorp.TurboLib {
class SomeClass { }
}
The full name of SomeClass
is FooBarCorp.TurboLib.SomeClass
.
If this is going to be a library, it should be placed in FooBarCorp.TurboLib.dll
.
One of the great advantages of Nemerle or C# over Java is that you don't need to
create any folder that matches the name of the namespace. Indeed, you can create
more than one namespace in a file:
namespace FuBar {
namespace Example {
// This is FuBar.Example
}
}
namespace OtherFoo {
// This is just OtherFoo
}
```
Nemerle requires all objects to be referenced using their fully qualified
name. To use <code>SomeClass</code>, you would have to reference <code>FooBarCorp.TurboLib</code>.
But, to avoid typing <code>FooBarCorp.TurboLib</code> again and again to access
its classes, there is a special <code>using</code> directive that ''imports''
the given namespace. So the following is valid:
```nemerle
using FooBarCorp.TurboLib;
def c = SomeClass ();
(Code inside the FooBarCorp.TurboLib
namespace doesn't require
using FooBarCorp.TurboLib;
-- that's done automatically.)
You can also use:
using FooBarCorp;
def c = TurboLib.SomeClass ();
One of the features Nemerle has is the ability to import a namespace and
use the namespaces inside it without qualifying them with the parent name.
In C#, you would have to add one or more using
directives to
for the classes you wish to use to get this effect.
Another form of the using
directive allows you to give a
shorter name to a namespace:
using TL = FooBarCorp.TurboLib;
def c = TL.SomeClass ();
Finally, using
is not restricted only to namespaces -- with
using
you can also reference a class or module so you can
use its static members:
using System.Console;
WriteLine ("Hey, we just used Console.WriteLine without writing Console!");
Enums are like restricted variants. Enums contain a list of options, and cannot have methods, fields, or constructors. Example:
enum Color {
| Red
| Green
| Blue
}
def string_of_color (e) {
match (e) {
| Color.Red => "red"
| Color.Green => "green"
| Color.Blue => "blue"
}
}
System.Console.WriteLine (string_of_color (Color.Red));
Enums are internally represented as integers, which makes them more efficient than variants. It also means you can convert integers to enums, but beware: if an integer doesn't represent a valid enum option, strange things can happen, so it's better to check a enum variable for allowed ranges before using it. For example;
System.Console.WriteLine (string_of_color (13 :> Color));
This will raise MatchFailure
exception.
However, the way enums work can help on some situations. One of the most used is to combine two or more values into one, using the | (OR) operator. For this, you must use values that do not overlap. Powers of 2 (1, 2, 4, ...) are ideal, because their individual bits do not coincide (in binary, 1 => 0b001, 2 => 0b010, 4 => 0b100), so the | operator can use them unambigously. The Mono and .NET guidelines also state that this type of enum should be marked with the Flags attribute:
using System;
[FlagsAttribute]
enum Seasons {
| Winter = 0b0001
| Spring = 0b0010
| Summer = 0b0100
| Fall = 0b1000
| Autumn = Fall
| AllSeasons = Winter + Spring + Summer + Fall
}
def seasons = Seasons.Winter | Seasons.Fall;
Console.WriteLine ($"Seasons: $(seasons.ToString())");
/* Output:
Seasons: Winter, Autumn
*/
As you can see in this example, you can also use more than one alias for the
same value, and even create combinations of values in the enum, like we have
done with AllSeasons
. Also, since we were able to use the
[FlagsAttribute]
, we saved ourselves the trouble of having to write
our own string_of_season function.
Normally, when you pass some value as a function parameter, a copy
of the value is created. This is call-by-value, and is preferred
because it makes certain that the function cannot modify the calling
code's values. But sometimes you do want the function to change the
original value passed to it, not the copy. This is possible by passing
the parameter using the ref
keyword. Such a parameter is
passed by reference, which means that when the parameter value is
changed by the function, the calling code sees it.
class C {
public static Inc (x : ref int) : void
{
x++;
System.Console.WriteLine (x);
}
public static Main () : void
{
mutable q = 13;
Inc (ref q);
Inc (ref q);
}
}
/* Output:
14
15
*/
First, you need to add a ref
annotation to the parameter type when
you declare the function. When you call the function, you need to pass
a mutable value (a local variable or a mutable field), and prefix it
with ref
keyword. Be aware that this just works with value types.
In Mono/.NET, there is a strong divison between reference and value types.
When you pass a reference type (object) as a parameter, you are only
passing a reference to an object, so every change to the object in the
function will also change the calling object. Value types (numbers, strings)
are always copied when passed, so you need the ref keyword to change this
default behavior.
There is also a similar out
modifier. It means that the function
is not going to read what's in the parameter, it is just going to return
some value through it. In C# this is handy when you want to return more
than one value from a function. In Nemerle, tuples
are used for the same purpose, so the use of out parameters is much less
common. Anyway, the usage is:
class C {
public static read_line (line : out string) : bool
{
line = System.Console.ReadLine ();
if (line == "quit") false
else true
}
public static Main () : void
{
mutable s = null;
while (read_line (out s)) {
System.Console.WriteLine (s);
}
}
}
This feature comes from Python. You can name the parameters in a function call, which allows putting them in a different order than in the definition. This can make your code more readable, especially when using default parameters.
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
This feature is particularly useful with default parameters:
frobnicate (foo : int, do_qux : bool = false,
do_baz : bool = false,
do_bar : bool = false) : int { ... }
frobnicate (3, do_baz = true);
frobnicate (3, do_baz = true, do_qux = true);
You can overload existing operators or add new ones simply by specifying
them as a static method in some type. The name of method must be quoted
with the @
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.
It is also possible to overload built-in operators like +
and
-
. Some of the classes exposed in the Base Class Library (like
Point) use this operator overloading. If you use them, you should also
provide another way to get its functionality, to mantain compatibility
with other languages. For example:
using Nemerle.Utility;
[Record]
class Vector {
[Accessor] x : double;
[Accessor] y : double;
[Accessor] z : double;
public Add (v : Vector) : Vector {
Vector (this.X + v.X, this.Y + v.Y, this.Z + v.Z)
}
public static @+ (v1 : Vector, v2 : Vector) : Vector {
v1.Add (v2)
}
}
def v1 = Vector (1, 2, 3);
def v2 = v1 + v1;
def v3 = v2.Add (v1);
However, some operators are implemented as macros in the standard library and therefor cannot be overloaded. They are: &&, ||, %||, %&&, %^^, +=, -=, *=, /=, <<=, >>=, %=, |=, &=, ^=, <->, ::=. Of these operators the operation-plus-assignment operators can be in effect overloaded by providing a non-assignment version. For instance, providing a "+" operator will also make "+=" work. This is because all the operation-plus-assignment operators expand to "LeftSide = LeftSide <operator></operator> RightSide".
The .NET Framework supports only single inheritance. This means that any given class can derive from just one base class. However, a class may sometimes be required to be two or more different things, depending on context. .NET supports this (just like Java) through interfaces. An interface is a contract specifying a set of methods that given class must implement. A class can implement any number of interfaces, in addition to deriving from some base class.
Implementing an interface implies subtyping it. That is, if you have a class A implementing interface I, and method taking I as parameter, then you can pass A as this parameter.
Interfaces most commonly state some ability of type. For example, the ability to convert itself to some other type, or to compare with some other types.
using Nemerle.IO;
interface IPrintable {
Print () : void;
}
class RefrigeratorNG : Refrigerator, IPrintable
{
public Print () : void
{
printf ("I'm the refrigerator!\n")
}
public this ()
{
}
}
module RP {
PrintTenTimes (p : IPrintable) : void
{
for (mutable i = 0; i < 10; ++i)
p.Print ()
}
Main () : void
{
def refr = RefrigeratorNG ();
PrintTenTimes (refr)
}
}
In the class definition, after the colon, the base class must come first, followed by the supported interfaces in any order.
There are several macros defined in the Nemerle standard library helping
improve code safety. The most basic one is the assert
macro known
from C. It is available by default (in Nemerle.Core
), no
using
is necessary. It checks if given expression is true
and if it's not, it throws an exception stating the expression, file
and line where the problem occurred. You can also supply a second
parameter to this macro with an error message you want to include
in the exception.
There are other macros, providing requires and ensures contracts, they are described in the wiki page about Design by contract macros.
Properties are syntactic sugar for get/set design pattern commonly found in Java. Accessing a property looks like accessing a field, but under the hood it is translated to a method call.
class Button {
text : string;
public Text : string {
get { text }
set { text = value; Redraw () }
}
}
def b = Button ();
b.Text = b.Text + "..."
Setter or getter blocks are not a must (of course, you need at least one :-). As we said in Week 1, for the traditional properties that just take care of saving or loading the value, the Accessor macro is preferred over plain writing.
Indexers are another form of property, but instead of exposing a simple field, array access syntax is used. They must take parameters, a feature that normal properties can't have. The main use of indexers is exposing collections to the outside, which is why the array syntax was chosen. All .NET indexers have names, though one indexer can be marked as the default indexer (well, in fact one indexer name is marked as default, but there can be several indexers with that name, subject to the regular overloading rules).
For example, the System.Hashtable
default indexer is called
Item
and System.String
one is called Chars
.
The C# language allows definition of a default indexer only. Nemerle allows
you to define other indexers (like VB.NET). In the current release, the
default indexer is always Item
.
class Table {
store : array [array [string]];
public Item [row : int, column : int] : string
{
get {
System.Console.WriteLine ($"get ($row, $column)");
store[row][column]
}
set {
System.Console.WriteLine ($"set ($row, $column)");
store[row][column] = value
}
}
public this ()
{
store = array (10);
for (mutable i = 0; i < store.Length; ++i)
store [i] = array (10);
}
}
def t = Table ();
t[2, 3] = "foo";
t[2, 2] = t[2, 3] + "bar";
/* Output:
set (2, 3)
get (2, 3)
set (2, 2)
*/
Delegates are half-baked functional values. There is little place for using delegates in Nemerle itself, because it supports true functional values. However other .NET languages all speak delegates, so they are good way to expose Nemerle functional values to them, and vice versa.
Delegates are in essence named functional types. They are defined with
delegate
keyword:
delegate Foo (_ : int, _ : string) : void;
delegate Callback () : void;
Later, the delegate name can be used to construct delegate instances. Any functional value of corresponding type can be used to create a delegate instance (local functions and lambda expressions in particular):
def my_fun (i : int, s : string) {
// do something with i and s
}
def my_Foo_delegate = Foo (my_fun);
def another_Foo_delegate = Foo (fun (i, s) { ... use i, s ... });
Delegate instances can be invoked using standard function call syntax,
as well as the Invoke
special method. Please consult the Base
Class Library documentation (on MSDN) for details.
The +=
operator has special meaning on delegate instances --
it calls the System.Delegate.Combine
method that makes one
function that calls two others in sequence. This operator is probably
more useful with events.
module X {
some_fun () : void
{
mutable f = Foo (fun (_, _) {});
f (3, "foo"); // same as f.Invoke (3, "foo");
f += fun (_, s) { System.Console.WriteLine (s) };
f (42, "bar");
}
}
Please note that creating a delegate is indeed creating a new class, so it can have the same level of visibility that you assign to any other class. You should also write delegates as stand-alone classes to prevent nesting.
You also have to think about accessibility when working with delegates. There is a requirement that if an event has a certain level of accessibility, then its delegate must have equal or greater accessibility.
This is a general rule in Nemerle (taken from C#), that when you define an entity E of type T, then access rights on E cannot be more permissive than access right on T. Otherwise it would be possible that a user would have access to E, but he wouldn't be able to use it, because the T would be inaccessible.
Events are specialized properties for delegates. They are used in a GUI system for connecting signals (setting function to call when a button is pressed, etc.). They can be also used to call user-defined functions on when certain things occur, like a class being loaded by the runtime system.
Events are defined like fields, but they are marked with the event
keyword, and always have a delegate type. Inside the class, events are seen
as fields of these delegate type, but outside the class only the +=
and -=
operators are available. They are used to connect and
disconnect delegates.
class Button {
public event OnClick : Callback;
}
def b = Button ();
// bind OnClick to some anonymous function ...
b.OnClick += Callback (fun () { ... })
Disconnecting is more tricky, as you cannot just do -= Callback (fun
() { ... })
. If you want to ever disconnect a delegate from an event,
you will need to store it:
def b = Button ();
def my_cb = Callback (fun () { ... });
b.OnClick += my_cb;
// ...
b.OnClick -= my_cb;
Implicit conversion from functional values to delegate instances is provided, so you can skip delegate creation:
def b = Button ();
b.OnClick += fun () { ... }
Keep two things in mind when using events:
- An event can only be invoked within the class that exposes it. If you need any other kind of functional values that interoperate well on .NET languages, use fields of delegate types.
- Events without any method attached are considered null values, and trying to invoke them will throw an
NullReferenceException
. So before using the method syntax for invoking an event, first check whether it is null.