-
Notifications
You must be signed in to change notification settings - Fork 89
Nemerle for OOP Programmers Week 1
During this lesson we will learn how to define good ol' classes in Nemerle. It works much like in C# (or Java, C++) but there are some differences.
We also take a look at loops and a few basic macros.
This week still doesn't introduce any functional features. We'll find out about them next week.
We assume you know what a class is.
Classes in Nemerle are defined with the class
keyword. The
basic form is:
class MyClass {
// something inside
}
There are several possible entities that can be defined inside classes. All of them can be proceeded by access modifiers, and some by other modifiers.
There are several kinds of modifiers. Note that you're probably only going
to use public
, static
and mutable
at first.
We will first discuss access modifiers. Modifiers specify the kind of
access other classes have to given entity. Access attributes are valid on
all kinds of entities defined in classes. Everything is private
by default. The public
and internal
modifiers are
also valid on top-level classes (which are internal by default).
-
public
- everyone can access -
internal
- only classes in the current assembly (DLL or EXE) can access -
private
- only current type can access -
protected
- access is limited to current type and its derived types -
protected
internal
- means protected or internal - access limited to derived types or types in the current assembly
-
static
- for fields, it means that there is only one instance of this field shared by all instances of the class. For methods (and properties) it means the implicitthis
argument is not passed, therefore the method can only use static fields and methods directly (instance methods/fields can be used, provided they are called on a specific object) -
mutable
- is valid only for fields and means that a field can be updated outside the constructor. There is no corresponding modifier in C# or Java; everything is mutable by default there. C# does have thereadonly
modifier to force fields to be non-mutable. -
volatile
- valid only for fields, it means that the field has to be always read from the memory, and written instantly (it avoids any caching in the JIT-ed code). It's mainly useful for multithreaded programs. -
extern
- used on methods, withDllImport
attributes to call out to native code. We probably won't cover this topic in this course. You can google on PInvoke for more information. -
partial
- valid only on type definitions, it means that a given type definition is split across several files. It works as if you defined several classes with the same name (and all with thepartial
modifier), but the compiler merges them together.
new
here. The others are described in the section
about virtual calls.
-
new
- valid on all entities (including nested types), it means to ignore a member with the same name in super class. Otherwise using the same name results in a warning. virtual
override
abstract
sealed
One way to think about the mutable
modifier is that it's much
like default-private fields. That is, you need to make a conscious decision
to make a field mutable, just as you need a conscious decision to make it
public. Therefore fields will end up non-mutable only when they need to.
As you continue programming in Nemerle, you will find yourself not needing
that much mutability.
Values created from classes are known as instances of the
class. Classes work like models, and instances are real representations
of that model in memory. If you want to access a instance method, that
is, a method not declared static
, you first have to create an
instance of the class using a constructor (see below). Then, you use the
. operator to "dive into" the class members: properties, fields,
methods...
On the other hand, static members can only be accessed through the name of the class. Trying to call a static method from an instance will always fail in Nemerle. Some other languages allow both uses, so be careful!
This is rather advanced topic, but this syntax is reused by top-level macros, so we briefly mention it here.
In .NET all metadata elements (classes, methods, fields and so on) can have a special, additional information attached. This information is (in most cases) not interpreted in any way by the runtime, therefore it is good place for placing documentation, authorship/copyright tags, and so on. This information can be accessed by user applications at runtime.
The syntax for attaching this information in Nemerle (and in C# for that matter) is:
[MyCustomAttribute (7, "foobar")]
public class FooBar {
[OtherCustomAttribute]
SomeMethod () : void
{
}
}
Custom attributes are classes subtyping System.Attribute
, so the
user can define new custom attributes, though some are provided by the
base class library.
If you're interested in this mechanism, it may be a good idea to consult MSDN attributes tutorial, after going through this course. It's about C#, but, except for minor syntactic differences it also applies to Nemerle.
In Nemerle, certain custom attributes trigger execution of macros - that is special compiler extensions. Later this week we will learn how to automatically create constructors and accessors.
Methods are used for grouping code together for later reuse. We've already seen how to define methods in the previous week.
As you recall, method calls are written using the method name, and then writing the parameters between parenthesis.
There is nothing special about fields in Nemerle (compared to fields
in C#/Java/C++), except for their immutability. If you do not declare
a field to be mutable
, they can be only set inside the class
or instance constructor (depending on if the field is static or not).
This is much like the readonly
modifier in C# or final
in Java.
You can also initialize fields at the place of their definition, which causes the appropriate initialization code to be added to the class or instance constructor.
Fields are defined with the following syntax:
modifiers name : type;
or with the initializer:
modifiers name : type = expression;
Modifiers are optional, and fields are private by default. If you omit the initializer fields have the value of 0, 0.0 or null for reference types.
In fact, even if you supply the initializer you can still observe this default value, for example:
class Test {
static public x : int = y + 1;
static public y : int = x + 2;
}
System.Console.WriteLine (Test.x);
System.Console.WriteLine (Test.y);
will print 1 and 3, because when initializing x
, y
still
has its default value of 0. As you can see, the initializations are performed
top-down.
Unlike in C#/Java/C++, the constructor has a fixed name, it's called
this
.
Example:
class Point {
public x : float;
public y : float;
public this (ini_x : float, ini_y : float)
{
x = ini_x;
y = ini_y;
}
}
Instead of inventing this ini_
stuff one can also use this
pointer directly:
class Point {
public x : float;
public y : float;
public this (x : float, y : float)
{
this.x = x;
this.y = y;
}
}
The constructor is later called by the class name:
def the_point = Point (3.14, -3.14);
There is no new
keyword, as is present in C# or Java.
If all your constructor is going to do is assign field values, which will
all supplied by the user, you can use the [Record]
macro.
So the example above can be reduced to:
[Record]
class Point {
public x : float;
public y : float;
}
This useful especially for creating small helper classes.
If you do not provide an instance constructor at all, the compiler adds a default, public (protected, for abstract classes), parameter-less, empty one. Therefore the following piece of code is valid:
class MyClass {}
def c = MyClass ();
Now, now that we know about fields, methods and constructors, we can start some examples. We will try to program a robot called Marvin.
class Marvin {
// Marvin starts with a fairly positive attitude
mutable attitude : int = 100;
mutable x : int;
mutable y : int;
mutable direction : int;
public this (ini_x : int, ini_y : int)
{
x = ini_x;
y = ini_y;
// head north
direction = 0;
}
}
This example is completely equivalent to:
class Marvin {
mutable attitude : int; // CHANGED
mutable x : int;
mutable y : int;
mutable direction : int;
public this (ini_x : int, ini_y : int)
{
attitude = 100; // CHANGED
x = ini_x;
y = ini_y;
// CHANGED
}
}
As you can see we moved initialization of attitude to the constructor.
This does exactly the same thing that the field initializer does. We also removed
direction = 0
, because 0 is the default value for int
anyway.
Now we can add some more complicated behavior to Marvin, we start with a
Turn
method:
public Turn (delta : int) : void
{
// we need to protect our engine, Marvin cannot
// turn too much at once!
if (delta < -90 || delta > 90)
System.Console.WriteLine ($ "Cannot turn that much ($delta degrees)!");
else {
// having to turn makes our robot feel worse
attitude -= 5;
direction += delta;
// call the robot's firmware here
}
}
(Because of our NDA with Sirius Cybernetics, we cannot reveal how to call Marvin's firmware to actually make it move, therefore we will just skip this :-)
The interesting part is the usage of the $
macro. It takes
a string as an argument, and interprets the dollar signs in it in a special
way -- it evaluates them as expressions, calls ToString()
on them
and puts the results into the string. If you need to place a more complicated
expression after $
, you need to use parens, like in:
System.Console.WriteLine ($ "Cannot turn that much ($(delta / (180 * 3.1415)) radians)!");
This works mostly like in PHP, perl or Bourne Shell, but it is checked at the compile time, so if you put an undefined variable you will get a compile time error, not empty string at runtime (check it out!).
The Framework provides also another way to interpolate
strings. Inside the string, you can include {n}
directives, and they will be changed with the "n + 2"-th
parameter. Console.WriteLine
is one of the methods that allow
this. In other places, one can use:
def addition = string.Format ("{0} + {1} = {2}", n1, n2, n1 + n2);
As you can imagine, it is easy to mess up matching the numbers, so better
just stick with $
:-)
Now we can add another method:
public TurnBack () : void
{
for (mutable i = 0; i < 2; ++i)
Turn (90);
}
The for
loop works much like other C-like languages -- the first
expression is evaluated before the loop, the second is the condition
and the last one is the increment.
So what about a general turning method without limits?
public TurnAny (mutable delta : int) : void
{
while (delta != 0)
if (delta > 90) {
Turn (90);
delta -= 90;
} else if (delta > 0) {
Turn (delta);
delta = 0;
} else if (delta < -90) {
Turn (-90);
delta += 90;
} else {
Turn (delta);
delta = 0;
}
}
As you can see, we have a while
loop too. Moreover, in order
to assign values to a parameter, we need to mark it mutable
.
A static constructor is executed once for the program (or more exactly, AppDomain) lifetime, when the class is first referenced. It's a good place to include some class-wide initialization code.
Initializers specified for static fields are added to the static constructor.
Static constructors need to be private, parameter-less and ergh... static. For example:
using System.Console;
class Hello {
static this ()
{
WriteLine ("static ctor");
}
static public Run () : void
{
WriteLine ("Run");
}
}
WriteLine ("hello");
Hello.Run ();
WriteLine ("bye");
/* Produces:
hello
static ctor
Run
bye
*/
Note how "hello" is written before static ctor is run, but before the Run method.
Properties provide language level support for the get/set accessor
design pattern commonly found in Java and C++. Most .NET languages (including
C#) provide properties. A property called Foo of type int is internally a set
of two methods get_Foo() : int
and set_Foo (value : int)
.
However instead of writing:
def my_value = some_object.get_Foo ();
some_object.set_Foo (42);
we write:
def my_value = some_object.Foo;
some_object.Foo = 42;
As you can see this is only syntactic sugar. There is also a special syntax for defining properties:
using System.Console;
class MyClass {
mutable m_foo : int;
public Foo : int
{
get {
WriteLine ("get_Foo called");
m_foo
}
set {
when (value > 3)
WriteLine ("value bigger than three");
m_foo = value;
}
}
}
def c = MyClass ();
c.Foo = 1;
WriteLine ($ "c.Foo = $(c.Foo)");
c.Foo += 42;
WriteLine ($ "c.Foo = $(c.Foo)");
/* Output:
get_Foo called
c.Foo = 1
get_Foo called
value bigger than three
get_Foo called
c.Foo = 43
*/
Note how +=
is changed to c.Foo = c.Foo + 42
, which
results in call to get_Foo
before setting the value.
Also note, that the set method has implicit parameter called value
.
You can omit get or set, but not both. If you omit set, trying to write to the property will result in an error. Omitting get means you can't read the property.
If your property is only going to return the value of a field, you
can use the [Accessor]
macro. It sits in Nemerle.Utility
.
You place it on a field. If you don't supply any parameters, it
generates a public getter:
using Nemerle.Utility;
class MyClass {
[Accessor]
foo_bar : string = "qux";
}
def c = MyClass ();
System.Console.WriteLine (c.FooBar);
/* Output: qux */
You can also supply another name for the accessor, request a setter, or make them internal. Please refer to accessor macros page for details.
Inheritance is one of the central concepts of OOP. It is a means of extending the functionality of an existing class. A class can inherit all the behavior of some original class, then extend it by adding new methods, fields and/or properties.
For example we can define a class called Robot
and later
define two classes inheriting from it: Marvin
and R2D2
.
Robot is then said to be supertype of Marvin and R2D2, and Marvin and
R2D2 are subtypes of Robot.
The inheriting class can also modify the behavior of the original class. This is done through virtual methods. For example, Robot can define a virtual SelfDestruct method, which R2D2 can override to refuse self-destruction, while Marvin can reuse the method from Robot (by just doing nothing).
The syntax is:
class Robot {
public virtual SelfDestruct () : void
{
// call robot firmware here
}
}
class R2D2 : Robot {
public override SelfDestruct () : void
{
System.Console.WriteLine ("refusing");
}
}
class Marvin : Robot {
// don't override anything
}
As you can see, the colon (:) is used to mark the class to inherit
(Java uses extends
keyword here). Methods need to marked
virtual
, otherwise they cannot be overridden (unlike in Java).
Additionally, the overriding method needs to be marked override
(unlike in C++).
Methods can also be marked abstract
. You mustn't provide any
body in this case. It means the method has to be eventually overridden by
an inheriting class. A class is marked abstract
when it has one
or more abstract methods, either defined in the class itself, or an
abstract method defined in the super type that is not overridden.
One cannot construct instances of abstract classes.
Example:
abstract class Robot {
abstract SelfDestruct () : void;
}
class R2D2 : Robot {
public override SelfDestruct () : void
{
System.Console.WriteLine ("refusing");
}
}
class Marvin : Robot {
public override SelfDestruct () : void
{
System.Console.WriteLine ("self destructing in 5 seconds...");
}
}
Another common pattern found in some C# classes, like Console
,
is to provide neither public nor default instance constructors, and make
all members static. This prevents every type of instantiation, and
works very well for resources where it doesn't make sense to have more than
one object at once (like console, file system in UNIX/Linux systems...).
In Nemerle, this pattern has a special construct: modules. A module is
declared exactly like a class, but every method or field you declared
on has an automagically added static
modifier. For example:
module Operations {
public Add (n1 : int, n2 : int) : int {
n1 + n2
}
public Substract (n1 : int, n2 : int) : int {
n1 - n2
}
}
// Note that module members are accessed through class name
System.Console.WriteLine (Operations.Add (1, 2));
Sometimes you need to convert an object from one type to other. You might
want to convert an int
to a double
, or a class to its
parent. In Nemerle, there are two conversion operators (or casting, as you
C# guys like to call them), depending on whether the cast is checked at
compile or runtime:
-
Type cast
( :> )
: this operator checks the type at runtime. It is especially useful when you receive a parent class instance from a method or property and you want to cast it to a derived class. However, as classes can have more than one child class, the conversion cannot be checked on compile time, and may throw aInvalidCastException
at runtime. -
Type enforcement
( : )
: you can only use this operator if the check is known to succeed at compile time. For example, conversion fromshort
toint
, or from one class to its parent.
def memoryStream = System.IO.MemoryStream ();
def stream = memoryStream : System.IO.Stream; // MemoryStream is a subclass of Stream
def n1 = int.Parse System.Console.ReadLine());
def n2 = int.Parse System.Console.ReadLine());
def resultAsShort = (n1 + n2) :> short; // We don't know if n1 + n2 fits on short, so we use :>
Create a Person
class. This class needs to have
FirstName
, LastName
and Telephone
properties,
as well as a BirthDate
. BirthDate
should be a record
Date
with the necessary fields. Additionally, add an Age
property that computes the age of the person using the system current date.
This property must not be writable.
Play a little with inheritance and virtual calls. The traditional example
is to create an Animal
class with Legs
property
and a virtual Speak
method. Then, create some more classes,
like Dog
, Cat
..., overriding the Speak
method. Watch the results!
Take this small example
and add ImperialTrooper
subclass to it. DarthVader
should
have a radio connection (a reference) to imperial trooper, so he can
ask (with a method) if he can see Luke.
Find out a little about XML Serialization (check MSDN, or Monodoc in
the System.Xml.Serialization
namespace), and write a method
for opening and saving a contact.