2 Application Development

Chapter 1 used a rather simple application as example. This chapter shows how to use functors for the modular development of larger applications.

2.1 Functors for Modular Applications

Principles of good software engineering suggest that larger applications should be designed and assembled from a collection of smaller modules. In Oz, this decomposition can be realized in terms of several functor definitions.

The primary purpose of a functor is to compute a module: It takes modules as input and computes a new module as output. As we have seen already, the import section of a functor specifies its inputs as a list of module names. In addition, functors may also have an export section which is basically a list of feature/value pairs that describes the module computed by the functor.

As demonstrated in Section 1.5 an application is run by executing the root functor. In our particular example, the root functor was rather simple in that it only imported system modules. However, larger applications will typically import modules computed by other application functors.

2.2 Example: Last Minute Flights

In the following we will build a trivial flight booking system featuring three components:

  1. A data base server: It maintains a data base that contains available flights, where each flight has a unique id by which it can be identified. At first, the data base is not even persistent, but as we incrementally refine and improve our application, the data base evolves into a persistent and distributed data base server.

  2. A graphical flight booking form, where a travel-minded user can choose a flight, enter her name, her E-mail address and so on. Later we will show how to build a web-based interface serving the same purpose.

  3. The main component of our application that manages user requests to the data base and sets up the application.

All components are programmed as functors.

2.3 The Data Base

Let us start with the data base, which is the most straightforward part of our application. The data will be held in a dictionary that uses integers as keys, and arbitrary data structures as entries. The functor definition resides in file DB.oz and its toplevel structure is as follows:

<DB.oz>=
functor 
  
<Export specification for DB.oz> 
  
<Body for DB.oz> 
end

The functor has no import specification, and its export specification is as follows:

<Export specification for DB.oz>=
export 
   add:    Add
   get:    Get
   getAll: GetAll
   remove: Remove

The specification determines that the functor's module provides the features add, get, getAll, and remove, where the value of each feature is given by the variable after the following colon. The values of these variables are then computed by the functor's body.

For convenience, the export specification above may also be written more succinctly as follows:

<Export specification for DB.oz (with syntactic sugar)>=
export 
   Add
   Get
   GetAll
   Remove

The shortcut to just use a variable identifier starting with a capital letter, defines both the variable identifier as well as the feature. The feature is the variable identifier with its first character changed to lowercase.

The functor body is of less importance to us here, however, you can find it in Section A.1. One advantage of modular program development is that during the design of an application one may concentrate first on finding the right interfaces, and only then provide corresponding implementations.

Even though the functor does not import any module, it uses predefined procedures (for example, Dictionary.new to create a new dictionary). The compiler provides a set of variable identifiers, that refer to the basic operations on all primitive Oz data types. This set of identifiers is known as the base environment and is documented in detail in ``The Oz Base Environment''.

When a functor definition is compiled, all free variable identifiers must be bound by the base environment.

2.4 The Graphical Input Form

The functor that implements the graphical form to book flights has the following structure, and its definition resides in file Form.oz:

<Form.oz>=
functor 
import 
   Tk
export 
   Book
define 
   proc {Book Fs ?Get}
      %% Takes a list of flights and returns the booked flight
      %% and information on the booking user
      
<Implementation of Book> 
   end 
end 

2.5 The Root Functor

The root functor for our last minute flights application uses the previously defined functors that maintain the data base and that provide the user form. The root functor's definition resides in file LMF.oz:

<LMF.oz>=
functor 
import 
   DB Form            % User defined
   System Application % System
define 
   %% Enter some flights
   {ForAll 
<Sample flights> DB.add}
   %% Book until all flights sold out
   proc {Book}
      case {DB.getAll}
      of nil then 
         {System.showInfo 'All flights sold.'}
      [] Fs then 
         O={Form.book Fs}
      in 
         {System.showInfo ('Booked: '#O.key# 
                           ' for: '#O.first# 
                           ' '#O.last# 
                           ' ('#O.email#')')}
         {DB.remove O.key}
         {Book}
      end 
   end 
   {Book}
   {Application.exit 0}
end 
       

2.6 Compilation

Functors also are compilation units. Each functor definition is compiled separately. For our example, the following sequence of commands

ozc -c DB.oz -o DB.ozf
ozc -c Form.oz -o Form.ozf
ozc -c LMF.oz -o LMF.oza

compiles our example functor definitions. If you now change the functor definition in, say, DB.oz but the interface of the created functor remains the same, none of the other functor definitions need recompilation.

Note that we have chosen as file extensions for pickled functors that are not supposed to be run as applications the string ozf. For the root functor of our application we chose oza. This is completely transparent as it comes to the semantics of our program, it is just a convention that makes it easier to tell apart which pickled functors are root functors of applications.

2.7 Execution

As before, we just execute the root functor of our application by applying the ozengine command to LMF.oza:

ozengine LMF.oza

The next chapter (Chapter 5) explains how applications that consist of several functors are executed.


Denys Duchier, Leif Kornstaedt and Christian Schulte
Version 1.4.0 (20080702)