MODULA-3

The reliable alternative to C++...

SAFE
SIMPLE
EFFECTIVE

[Text from a brochure by Pine Creek Software.]
Copyright 1991-1992 Pine Creek Software. All rights reserved.


COMMON QUESTIONS ABOUT MODULA-3
===============================

.c1.What will Modula-3 do for me?;

Modula-3 gives you modern programming tools with increased safety and 
maintainability. You can be more productive, and your applications can be more 
robust. Readable and maintainable code does not depend on exceptional 
discipline by individual programmers.

.c1.How is it related to Pascal and Modula-2?;

Pascal and Modula-2 are used mainly in teaching. Modula-3 was designed by 
Digital and Olivetti for the real world. It includes many modern and pragmatic 
features. It is a new language and not an extension of Modula-2.

Modula-3 is in the public domain; anyone can use it or implement it.

.c1.What does Modula-3 provide?;

Objects, threads and generics provide new abstraction capabilities. 
Exceptions, garbage collection and the isolation of unsafe code make programs 
safer and more robust. A clean type system keeps the language simple and 
internally consistent.

.c1.How complicated is Modula-3?;

It is much simpler than other languages of comparable power, including Ada and 
C++. The entire reference manual is barely over 50 pages long.

.c1.How stable is Modula-3?;

Modula-3 is more stable than Ada or C++, both of which are undergoing 
significant revisions and have no final reference yet. The complete 
description of Modula-3 is published and there are no changes planned. 
.c1.What advantages does Modula-3 have over C++?;

Separately-compilable interfaces, concurrency via threads, automatic garbage 
collection, sets, open arrays, the isolation of unsafe code, and freedom from 
unchecked run-time errors. Common  features tend to be simpler and safer in 
Modula-3.

.c1.What does C++ have that Modula-3 does not?;

Multiple inheritance, constructors and destructors, user-defined operators, 
and overloaded functions. Common features tend to have more "bells and 
whistles" in C++. However, their  value has not been demonstrated in practice, 
and they increase complexity and reduce readability.

.c1.Won't I have to give up C++'s low-level facilities?;

No. Modula-3 offers unsigned arithmetic, bit-level operators, packed types, 
unchecked type conversions, address arithmetic, and pointers not subject to 
garbage collection. Some features are allowed only in UNSAFE modules, 
isolating nonportable code.

.c1.Won't all those rigid rules keep me from getting my job done?;

Many people who complain about structured languages like to write programs "on 
the fly," without planning ahead. That's OK for small programs, but not for 
the large ones for which Modula-3 was designed. When you take shortcuts with 
other languages, your program becomes more obscure, and obscurity hides bugs. 
Take a little extra time and do it right. It will save you--or someone else--
time later.


CODE COMPARISON
===============

Here is a brief comparison of C++ and Modula-3 programs. Of course, a small 
example is not enough to completely appreciate a language, and we hope you'll 
try out Modula-3 for yourself. This example was taken and modifed from 
Stroudstrup, The C++ Programming Language, Addison-Wesley, 1986.

.c1.Interface Declarations;

In C++, header files contain class and type declarations. Classes must expose 
both public and private components. Constructors and destructors are declared:

struct name {
   char * string;
   name* next;
   double value; };
class table {
   name ** tbl;
   int size;
public:
   table(int sz = 15);
   ~table();
   name* Lookup(char *);
};

In Modula-3 interfaces, classes expose only their public parts (an ancestor 
type). All objects are represented as pointers. Initialization functions 
corresponds to C++ constructors.

INTERFACE Table;
TYPE
   Name = RECORD
      string: TEXT;
      next: REF Name;
      value: LONGREAL;
   END; 
   T <: PublicT;  (* T is an opaque class *)
   PublicT = OBJECT
   METHODS
      init(size: CARDINAL := 15): T;
      lookup(val: TEXT): REF Name;
   END;
END Table.

The concrete representation of the Modula-3 class is revealed in aseparate 
module:

MODULE Table;
TYPE  HashTable = REF ARRAY OF REF Name;
REVEAL 
   T = PublicT OBJECT
      tbl: HashTable := NIL;
   OVERRIDES
      init := Init;
      lookup := Loockup;
   END;

.c1.Clients, constructors, initialization;

In C++, clients declare objects like normal variable declarations:

#include "table.h"
table keywords;
table identifiers(100);

This causes an implicit call on the constructor:

table::table(int sz)
{
   if (sz < 0) error("negative table size");
   tbl = new name*[size = sz];
   for (int i = 0; i < sz; i++) tbl[i] = 0;
}

In Modula-3, all objects are dynamically allocated. Clients call the 
initialization function for the class explicitly.

IMPORT Table;
VAR
   keywords := NEW(Table.T).init();
   identifiers := NEW(Table.T).init(100);

Init creates the hash table array. Unlike C++, the size of the table does not 
have to be recorded; it is kept with the array:

PROCEDURE Init(self:T; size:CARDINAL:=15) =
   BEGIN
      self.tbl := NEW(HashTable, size);
   END Init;

In C++, when an object is freed the destructor is automatically called:

table::~table()
{
   for (int i = 0; i < size; i++)
      for (name* n = tbl[i]; n; n=n->next) {
         delete n->string;
         delete n;
      }
   delete tbl;
}

In Modula-3, automatic garbage collection removes the need to free storage 
explicitly.

.c1.Bits and Characters;

C++ inherits C's built-in bit and character manipulation functions. Here is a 
function that maps a string to an integer in the range [0..size-1]:

int Hash(char *str, int size)
{
   int hash = 0;
   while (*str) hash = hash<<1 ^ *str++;
   if (hash < 0) hash = -hash ;
   return hash % size;
}

The corresponding Modula-3 function uses procedures from the built-in Word and 
Text interfaces. The result is larger, but easier to understand. The library 
calls can be expanded inline:

IMPORT Word, Text; ...
PROCEDURE Hash(str: TEXT; size: CARDINAL)
   : CARDINAL =
   VAR hash: Word.T := 0;
   BEGIN
      FOR i := 1 to Text.Length(str) DO
         hash:= Word.Xor(hash, 
            ORD(Text.GetChar(str, i)));
         hash:= Word.Shift(hash, 1);
      END;
      RETURN ABS(hash) MOD size;
   END Hash;

.c1.Lookup Function;

The Lookup function searches the hash table for a name, adding it if 
necessary.

name* table::Lookup(char *val)
{
   int hash = Hash(val, size);
   for (name* n = tbl[hash ]; n; n = n->next)
      if (strcmp(val, n->string) == 0) return n;
   name*nn = new name;
   nn->string = new char[strlen(val)+1];
   strcpy(nn->string, val);
   nn->value = 1;
   nn-> next = tbl[hash ];
   tbl[hash ] = nn;
   return nn;
}
The Modula-3 version of Lookup is similar. The WITH statement establishes an 
alias for the hash table bucket for the string being retrieved or inserted. 
Unlike C++, object components are not made directly visible in member 
functions. For better readability, you reference them via the first parameter 
(here called self). Also, it is not necessary to copy TEXT values, since they 
are immutable.

PROCEDURE Lookup(self: T; val: TEXT)
   : REF Name =
   VAR  name: REF Name;
   BEGIN
   WITH bucket = self.tbl[Hash(val, 
         NUMBER(self.tbl^))]  DO
      name := bucket;
      WHILE name # NIL DO
         IF Text.Equal(name.string, val) THEN
            RETURN name;
         END;
         name := name.next;
      END;
      (* Insert new entry *)
      bucket := 
         NEW(REF Name, string := val, 
            next := bucket);
      RETURN bucket;
   END;
   END Lookup;
