Programming in Modula-3 (at UW)

This page describes a few of the coding conventions that we use in writing code for the SPIN kernel, kernel extensions, and user-level code. Every organization adopts its own coding style, and the issue more important than the details of the actual style is that the style is uniformly followed. Doing so makes it easier for others to understand your program, and therefore more difficult to make mistakes based on what they read.

In most cases, I'll try to make my point by example and then justification (if there is one), rather than by a long winded description of how things ought to look. In a few cases, I'll describe in detail the use of some of the more obscure (but useful) Modula-3 mechanisms, specifically opacity and partial revelation.

Identifiers

An identifier describes a variable, a constant or a procedure name. Here is a short snipped from an interface shows what they should look like:
 

CONST
    NumberOfStars = 100;
VAR
    INTEGER allGoodBoysGoToHeaven: 

PROCEDURE GoodProcedure(a: INTEGER; theCat: CARDINAL) : INTEGER;
Specifically,

FROM

Please try to avoid using FROM in your IMPORTs. It makes it a lot harder to perform global searches on symbols and interfaces. While it makes for a bit faster typing at times, it transfers more of the burden of understanding your code to the reader.

File names

File names should be named like procedures, eg Main.m3, GoodTimes.m3, GoodTimes.i3. There are no underscores.

Interface names are little trickier, since there may not be a one to one correspondence between module names and interface names (as is the case when there is more than one interface to a single file). Suppose there is a module Disk, then some interfaces we might have are:

Disk.i3
The primary public interface to Disk. Most people who want to know about Disks come her.
DiskPrivate.i3
A private interface to Disk. Privileged procedures go in here. Typically, we would place an Initialization procedure in this interface.
DiskRep.i3
The representation of any not generally public types associated with Disks. You might put the opaque definition of a Disk inside Disk.i3, and then include in DiskRep.i3 a partial or complete revelation. Your file name should indicate the resource that the file describes. Although this may sound silly, all filenames should be in English.

The Extra Interface

On occasion, you may need to use the "Extra" interface which helps out when you are reluctant to change the primary. For example, suppose you need to add a new function to the interface CastInStone.i3. Reluctant to make changes to that interface, you define a new interface: CastInStoneExtra.i3 which calls attention to the fact that you've changed something which you probably shouldn't.

File headers and whist entries

Every file we build here should contain the standard copyright header:
(*
 * Copyright 1994, 1995 University of Washington
 * All rights reserved.
 * See COPYRIGHT file for a full description
 *
 * DESCRIPTION
 *	This file does this...
 *
 * HISTORY
 * 03-Aug-95  Joe Someone (someone) at the University of Washington
 *      Created
 *)

The HISTORY component is important because that is what whist uses to timestamp updates. If you change a file, you must whist it indicating what changed.

Interface Specifiers and Versions

For public interfaces where it's likely that there may be more than one interface of the same name, or more than one version of the same interface, it is useful to include a CONST Brand in the interface that disambiguates the specific instance, as in:
INTERFACE Widget;

CONST Brand = "Widget-1.0";
...
END Widget.

Generics

There are a large number of predefined generic interfaces that come packaged with the SRC distribution. Please try to use those. Many generics have a built in naming convention generated by the quake directives. Where possible, rely on those. For example, don't make a RefTable.i3, instead use your m3makefile to automatically create the RefTbl interface. Try to define any special data interfaces in terms of generics that already exist. For example, we have a generic Table interface, and we should soon have a generic NonBlockingTable interface.

Types

The names of types should be capitalized vis a vis procedure names. If an interface, say I, exports a primary type, then the name of that type should be T, giving rise to "I.T". Naming of secondary and tertiary exported types is less important.

Objects and methods

Instance variables and methods in an object declaration should be named as regular Modula-3 variables, that is: first character lower case, first character of remaining words upper case, as in:
INTERFACE Screen;
TYPE T = OBJECT
   x,y: INTEGER;
METHODS
   writeChar(x,y: INTEGER; t: TEXT);
END;
END Screen.

Opacity and Revelations

We've no real conventions for describing super and sub relationships. The "IRep" convention is one good way to advertise the structure of a partially or fully revealed type.

Defining and Ordering Type Definitions

Defining partially revealed types is often confusing because often the public names act only as place holders to describe ordering relationships among the super and subtypes. (For a good discussion on partial revelations, see the article "Partial Revelation and Modula-3").

If you only want to identify an object as having an identifiable type with no fields or methods, then define it as a subtype of ROOT (for objects) or REFANY (for reference types).

INTERFACE I;
TYPE T <: ROOT;
TYPE T1 <: REFANY;
END I.

Note that you REFANY is the only legitimate supertype for a REF type. Object types can have any ancestral relationship you like.

Within a scope where you wish to reveal the internal representatio of (say) T, you would need:

REVEAL T = ROOT OBJECT ...
In this case, the specification of the supertype ROOT is unnecessary, since ROOT is a supertype of all objects. However, this simple example makes it easier to understand the more common use of types and revelations.

Usually, we want to be more restrictive about the opaque types within an interface. The basic approach is to define a public supertype of the type you wish to make opaque:

INTERFACE I;

TYPE SomeVisibleAncestorType = OBJECT
      x: INTEGER;
END;

TYPE T <: SomeVisibleAncestorType;
And to elsewhere reveal the representation of T as:
MODULE I;
REVEAL T = SomeVisibleAncestorType BRANDED OBJECT
      y: INTEGER;
END I.
Clients of interface I can access the field "x" of instances of type T, which is a subtype of SomeVisibleAncestorType.

In effect, this approach says "SomeVisibleAncestorType" is a prefix of "T" and that some additional fields are revealed in T's suffix.

It is possible to leave room in both the prefix and suffix of T by declaring a placeholder type that occupies the part of T between the prefix and the suffix. Revelations later describe the contents of both.

INTERFACE I;
TYPE T1 <: ROOT;	(* establish a prefix of T2 and T3 *)

TYPE T2 = T1 OBJECT     (* define what goes between T2 and T3 *)
        y: INTEGER;
END;

TYPE T3 <: T2;		(* establish a suffix of T3 *)
Now, we can reveal information about T1, and T3 in separate places:
MODULE I;

REVEAL T1 = BRANDED OBJECT
        x: INTEGER;
END;

REVEAL T3 = T2 BRANDED OBJECT
        z: INTEGER;
END;
What we're saying in the interface is: "we've got this thing called T1 that has some fields in it, but I won't tell you what. But, I will tell you about this thing called T2 that has a 'y' in it. There's also a T3, which has a T2 in it, but I won't tell you anything else. Then, in the implementation module, we announce that T1 has an x in it, and T3 has a z in it. All T2's have a y, as do all T3's.

Externals

DEC SRC Modula-3 supports the <* EXTERNAL *> pragma inside safe modules. We have changed the compiler to restrict use to UNSAFE modules. As a convention, you should place all of your externals inside an interface with the Extern suffix. For example, we have an UNSAFE INTEFACE SALExtern.i3 that contains all of the externs require by the SAL modules. Few clients import SALExtern. Instead, they import interfaces which define wrapper functions for the SALExtern services.

SAFETY

Every UNSAFE module and interface must contain a comment at the top which clearly states why the UNSAFE attribute was required. It makes you think.

Do not use the capitalized forms LOOPHOLE, UNSAFE, or UNTRACED, or ADR in comments in your programs. Using a variant with some lowercase letters in it makes it easier to scan a large program for unsafe constructs.

Machine specific code

Place all machine dependent code inside the alpha subdirectory. Eventually we're going to port this baby to a Pentium.