What is the purpose of BRANDED and REVEAL?

Allan Heydon writes:

These two keywords are necessary because of two quite different features of the language. REVEAL is necessary because Modula-3 has opaque types and partial revelations. BRANDED is necessary because the Modula-3 type system uses structural equivalence instead of name equivalence.

In Modula-3, the concrete structure of a type can be hidden from clients in an interface. A common idiom is:


  INTERFACE I;

  TYPE
    T <: TPublic;
    TPublic = OBJECT
      (* fields *)
    METHODS
      (* methods *)
    END;

  END I.

The line "T <: TPublic" introduces the type "I.T" as an opaque subtype of the type "I.TPublic". It does not reveal any of the other details of the concrete structure of "I.T" to clients. Hence, "I.T" is said to be an opaque type. Put another way, the structure of "I.T" is only partially revealed to clients.

In addition, it is possible to reveal more of "I.T"'s structure in other interfaces, like this:


  INTERFACE IRep;

  IMPORT I;

  TYPE
    TPrivate = I.TPublic OBJECT
      (* more fields *)
    METHODS
      (* more methods *)
    END;

  REVEAL
    I.T <: TPrivate;

  END IRep.

This interface declares a type "IRep.TPrivate" that is a subtype of "I.TPublic". It also asserts that "I.T" is also a subtype of "IRep.TPrivate". A client that imports only the interface "I" has access only to the fields and methods in "I.TPublic" when accessing an object of type "I.T", but a client that imports both "I" and "IRep" also has access to the fields and methods in "IRep.TPrivate" when accessing an object of type "I.T".

The "REVEAL" statement in this module simply asserts a subtype relation. Unlike type declarations, revelations introduce no new names. Hence, we could not have used the "TYPE" keyword in this case because the type "I.T" has already been declared once (albeit opaquely) in interface "I".

Every opaque type must have a complete revelation. A complete revelation has the form:


  REVEAL
    T = TConcrete;

The revelation specifies that "TConcrete" is the concrete type for the opaque type "T".

The Modula-3 type system uses structural equivalence instead of name equivalence. This means that two types are equal iff they have the same structure. One consequence of this rule is that two types you might intend to be distinct may actually be equal. This can have unintended effects on the run-time behavior of your program. For example, if both types that you expect to be distinct are actually structurally equivalent and the two types guard two arms of a TYPECASE statement, the arm for the second type will never be taken.

If you want to avoid accidental equalities between two types, you can brand one (or both) of them with the BRANDED keyword. A branded type is equivalent to no other type, even if it is structurally equivalent to some other type. In essence, the BRANDED keyword adds a bit of virtual structure to the type that guarantees it will be distinct from every other type.

The Modula-3 syntax allows you to supply a text constant as a name for the brand. If you don't supply an explicit brand, the compiler will make one up; however, the implicit brand invented by the compiler is not guaranteed to be chosen deterministically. Hence, explicit brands are useful if you are communicating types from one process to another and if you want to be sure that the branded type written by one process matches the branded type read in by the other.

Any two opaque types in a program must be distinct. Otherwise, it would be too easy for clients to accidentally trip over type collisions like the TYPECASE example mentioned above. To enforce the restriction that all opaque types are distinct, the language requires that the type "TConcrete" in the complete revelation above must be a branded type.