Skip to content

Instantly share code, notes, and snippets.

@mrb
Created August 1, 2013 20:57
Show Gist options
  • Save mrb/6135227 to your computer and use it in GitHub Desktop.
Save mrb/6135227 to your computer and use it in GitHub Desktop.

3.9.3 Software components

What is a good way to organize a program? One could write the program as one big monolithic whole, but this can be confusing. A better way is to partition the program into logical units, each of which implements a set of operations that are related in some way. Each logical unit has two parts, an interface and an implementation. Only the interface is visible from outside the logical unit. A logical unit may use others as part of its implementation.

A program is then simply a directed graph of logical units, where an edge between two logical units means that the first needs the second for its imple- mentation. Popular usage calls these logical units “modules” or “components”, without defining precisely what these words mean. This section introduces the basic concepts, defines them precisely, and shows how they can be used to help design small declarative programs. Section 6.7 explains how these ideas can be used to help design large programs.

We call module a part of a program that groups together related operations into an entity that has an interface and an implementation. In this book, we will implement modules in a simple way:

• The module’s interface is a record that groups together related language en- tities (usually procedures, but anything is allowed including classes, objects, etc.).

• The module’s implementation is a set of language entities that are accessible by the interface operations but hidden from the outside. The implementa- tion is hidden using lexical scoping.

We will consider module specifications as entities separate from modules. A module specification is a kind of template that creates a module each time it is instantiated. A module specification is sometimes called a software component. Unfortunately, the term “software component” is widely used with many different meanings [187]. To avoid any confusion in this book, we will call our module specifications functors. A functor is a function whose arguments are the modules it needs and whose result is a new module. (To be precise, the functor takes module interfaces as arguments, creates a new module, and returns that module’s interface!) Because of the functor’s role in structuring programs, we provide it as a linguistic abstraction. A functor has three parts: an import part, which specifies what other modules it needs, an export part, which specifies the module interface, and a define parts, which gives the module implementation including its initialization code. The syntax for functor declarations allows to use them as either statements or expressions, like the syntax for procedures. Table 3.7 gives the syntax of functor declarations as statements.

In the terminology of software engineering, a software component is a unit of independent deployment, a unit of third-party development, and has no persistent state (following the definition given in [187]). Functors satisfy this definition and are therefore a kind of software component. With this terminology, a module is a component instance; it is the result of installing a functor in a particular module environment.

⟨statement⟩ ::= functor ⟨variable⟩
[ import { ⟨variable⟩ [ at ⟨atom⟩ ]
| ⟨variable⟩  ́( ́ { (⟨atom⟩ | ⟨int⟩) [  ́: ́ ⟨variable⟩ ] }+  ́) ́
}+ ]
[ export { [ (⟨atom⟩ | ⟨int⟩)  ́: ́ ] ⟨variable⟩ }+ ] define { ⟨declarationPart⟩ }+ [ in ⟨statement⟩ ] end
| ...

 The module environment consists of a set of modules, each of which may have an execution state. Functors in the Mozart system are compilation units. That is, the system has support for handling functors in files, both as source code (i.e., human-readable text) and object code (i.e., compiled form). Source code can be compiled, or translated, into object code. This makes it easy to use functors to exchange software between developers. For example, the Mozart system has a library, called MOGUL (for Mozart Global User Library), in which third-party developers can put any kind of information. Usually, they put in functors and applications.

An application is standalone if it can be run without the interactive interface. It consists of a main functor, which is evaluated when the program starts. It imports the modules it needs, which causes other functors to be evaluated. The main functor is used for its effect of starting the application and not for its resulting module, which is silently ignored. Evaluating, or “installing”, a functor creates a new module in three steps. First, the modules it needs are identified. Second, the initialization code is executed. Third, the module is loaded the first time it is needed during execution. This technique is called dynamic linking, as opposed to static linking, in which the modules are loaded when execution starts. At any time, the set of currently installed modules is called the module environment.

Implementing modules and functors

Let us see how to construct software components in steps. First we give an example module. Then we show how to convert this module into a software component. Finally, we turn it into a linguistic abstraction.

Example module In general a module is a record, and its interface is accessed through the record’s fields. We construct a module called MyList that provides interface procedures for appending, sorting, and testing membership of lists. This can be written as follows:

declare MyList in local
proc {Append ... } ... end
proc {MergeSort ...} ... end
proc {Sort ... } ... {MergeSort ...} ... end proc {Member ...} ... end
in
MyList= ́export ́(append: Append
end
sort: Sort member: Member ...)

The procedure MergeSort is inaccessible outside of the local statement. The other procedures cannot be accessed directly, but only through the fields of the MyList module, which is a record. For example, Append is accessible as MyList.append. Most of the library modules of Mozart, i.e., the Base and System modules, follow this structure. A software component Using procedural abstraction, we can turn this mod- ule into a software component. The software component is a function that returns a module:

fun {MyListFunctor}
proc {Append ... } ... end
proc {MergeSort ...} ... end
proc {Sort ... } ... {MergeSort ...} ... end proc {Member ...} ... end
in
 ́export ́(append: Append
sort: Sort member: Member ...)
end

Each time MyListFunctor is called, it creates and returns another MyList mod- ule. In general, MyListFunctor could have arguments, which are the other modules needed for MyList. From this definition, it is clear that functors are just values in the language. They share the following properties with procedure values:

• A functor definition can be evaluated at run time, giving a functor. • A functor can have external references to other language entities. For ex- ample, it is easy to make a functor that contains data calculated at run time. This is useful, for example, to include large tables or image data in source form. • A functor can be stored in a file by using the Pickle module. This file can be read by any Mozart process. This makes it easy to create libraries of third-party functors, such as MOGUL. • A functor is lightweight; it can be used to encapsulate a single entity such as one object or class, in order to make explicit the modules needed by the entity.

Because functors are values, it is possible to manipulate them in sophisticated ways within the language. For example, a software component can be built that implements component-based programming, in which components determine at run time which components they need and when to link them. Even more flexibility is possible when dynamic typing is used. A component can link an arbitrary component at run time, by installing any functors and calling them according to their needs.

Linguistic support This software component abstraction is a reasonable one to organize large programs. To make it easier to use, to ensure that it is not used incorrectly, and to make clear the intention of the programmer (avoiding confusion with other higher-order programming techniques), we turn it into a linguistic abstraction. The function MyListFunctor corresponds to the following functor syntax:

   functor
   export
      append:Append
      sort:Sort
      member:Member
      ...
define
proc {Append ... } ... end
proc {MergeSort ...} ... end
proc {Sort ... } ... {MergeSort ...} ... end proc {Member ...} ... end
end

Note that the statement between define and end does implicit variable decla- ration, exactly like the statement between local and in.

Assume that this functor has been compiled and stored in the file MyList.ozf (we will see below how to compile a functor). Then the module can be created as follows in the interactive interface:

declare [MyList]={Module.link [ ́MyList.ozf ́]}

The function Module.link is defined in the System module Module. It takes a list of functors, loads them from the file system, links them together (i.e., evaluates them together, so that each module sees its imported modules), and returns a corresponding list of modules. The Module module allows doing many other operations on functors and modules.

Importing modules Software components can depend on other software com- ponents. To be precise, instantiating a software component creates a module. The instantiation might need other modules. In the new syntax, we declare this with import declarations. To import a library module it is enough to give the name of its functor. On the other hand, to import a user-defined module requires stating the file name or URL of the file where the functor is stored.20 This is reasonable, since the system knows where the library modules are stored, but other naming schemes are possible, in which functors have some logical name in a compo- nent management system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment