Nemerle for OOP Programmers Week 5 - vilinski/nemerle GitHub Wiki

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.

Table of Contents

Namespaces and the using directive

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

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.

Function parameters

Ref/out parameters

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);
    }
  }
}

Named parameters

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
Named parameters can be used only when names of parameters are known (which basically means they do not work in conjunction with functional values).

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);

Operator overloading

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".

Interfaces

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.

Design by contract macros

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

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

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

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 +&#61; 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

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 +&#61; and &#45;&#61; 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 &#45;&#61; Callback (fun () &#123; ... &#125;). 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.
ToC
⚠️ **GitHub.com Fallback** ⚠️