Google

PLT MzLib: Libraries Manual


unit.ss: Core Units

MzScheme's units are used to organize a program into separately compilable and reusable components. A unit resembles a procedure in that both are first-class values that are used for abstraction. While procedures abstract over values in expressions, units abstract over names in collections of definitions. Just as a procedure is invoked to evaluate its expressions given actual arguments for its formal parameters, a unit is invoked to evaluate its definitions given actual references for its imported variables. Unlike a procedure, however, a unit's imported variables can be partially linked with the exported variables of another unit prior to invocation. Linking merges multiple units together into a single compound unit. The compound unit itself imports variables that will be propagated to unresolved imported variables in the linked units, and re-exports some variables from the linked units for further linking.

In some ways, a unit resembles a module (see Chapter 5 in PLT MzScheme: Language Manual), but units and modules serve different purposes overall. A unit encapsulates a plugable component -- code that relies, for example, on ``some function f from a source to be determined later.'' In contrast, if a module imports a function, the import is ``the function f provided by the specific module m.'' Moreover, a unit is a first-class value that can be multiply instantiated, each time with different imports, whereas a module's context is fixed. Finally, because a unit's interface is separate from its implementation, units naturally support mutually recursive references across unit boundaries, while module imports must be acyclic.

MzScheme supports two layers of units. The core unit system comprises the unit, compound-unit, and invoke-unit syntactic forms. These forms implement the basic mechanics of units for separate compilation and linking. While the semantics of units is most easily understood via the core forms, they are too verbose for specifying the interconnections between units in a large program. Therefore, a system of units with signatures is provided on top of the core forms, comprising the define-signature, unit/sig, compound-unit/sig, and invoke-unit/sig syntactic forms.

The core system is described in this chapter, and defined by the unit.ss library. The signature system is described in section 35, and defined by unitsig.ss. Details about mixing core and signed units are presented in section 35.9 (using procedures from unitsig.ss).

34.1  Creating Units

The unit form creates a unit:

(unit 
  (import variable ···) 
  (export exportage ···unit-body-expr 
  ···) 
 
exportage is one of 
  variable 
  (internal-variable external-variable

The variables in the import clause are bound within the unit-body-expr expressions. The variables for exportages in the export clause must be defined in the unit-body-exprs as described below; additional private variables can be defined as well. The imported and exported variables cannot occur on the left-hand side of an assignment (i.e., a set! expression).

The first exportage form exports the binding defined as variable in the unit body using the external name variable. The second form exports the binding defined as internal-variable using the external name external-variable. The external variables from an export clause must be distinct.

Each exported variable or internal-variable must be defined in a define-values expression as a unit-body-expr.11 All identifiers defined by the unit-body-exprs together with the variables from the import clause must be distinct.

Examples

The unit defined below imports and exports no variables. Each time it is invoked, it prints and returns the current time in seconds:12

(define f1@ 
  (unit (import) (export) 
   (define x (current-seconds)) 
   (display x) 
   (newlinex)) 

The unit defined below is similar, except that it exports the variable x instead of returning the value:

(define f2@ 
  (unit (import) (export x) 
   (define x (current-seconds)) 
   (display x) 
   (newline))) 

The following units define two parts of an interactive phone book:

(define database@ 
  (unit  
    (import show-message) 
    (export insert lookup) 
 
    (define table (list)) 
    (define insert 
      (lambda (name info) 
        (set! table (cons (cons name info) table)))) 
    (define lookup 
      (lambda (name) 
        (let ([data (assoc name table)]) 
          (if data 
              (cdr data) 
              (show-message "info not found"))))) 
    insert)) 
 
(define interface@ 
  (unit  
    (import insert lookup make-window make-button) 
    (export show-message) 
    (define show-message 
      (lambda (msg) ...)) 
    (define main-window 
      ...))) 

In this example, the database@ unit implements the database-searching part of the program, and the interface@ unit implements the graphical user interface. The database@ unit exports insert and lookup procedures to be used by the graphical interface, while the interface@ unit exports a show-message procedure to be used by the database (to handle errors). The interface@ unit also imports variables that will be supplied by an platform-specific graphics toolbox.

34.2  Invoking Units

A unit is invoked using the invoke-unit form:

(invoke-unit unit-expr import-expr ···

The value of unit-expr must be a unit. For each of the unit's imported variables, the invoke-unit expression must contain an import-expr. The value of each import-expr is imported into the unit. More detailed information about linking is provided in the following section on compound units.

Invocation proceeds in two stages. First, invocation creates bindings for the unit's private, imported, and exported variables. All bindings are initialized to the undefined value. Second, invocation evaluates the unit's private definitions and expressions. The result of the last expression in the unit is the result of the invoke-unit expression. The unit's exported variable bindings are not accessible after the invocation.

Examples

These examples use the definitions from the earlier unit examples in section 34.1.

The f1@ unit is invoked with no imports:

(invoke-unit f1@) ; => displays and returns the current time 

Here is one way to invoke the database@ unit:

(invoke-unit database@ display

This invocation links the imported variable message in database@ to the standard Scheme display procedure, sets up an empty database, and creates the procedures insert and lookup tied to this particular database. Since the last expression in the database@ unit is insert, the invoke-unit expression returns the insert procedure (without binding any top-level variables). The fact that insert and lookup are exported is irrelevant to the invocation; exports are only used for linking.

Invoking the database@ unit directly in the above manner is actually useless. Although a program can insert information into the database, it cannot extract information since the lookup procedure is not accessible. The database@ unit becomes useful when it is linked with another unit in a compound-unit expression.

(define-values/invoke-unit (export-id ···) unit-expr [prefix import-id ···])      SYNTAX

This form is similar to invoke-unit. However, instead of returning the value of the unit's initialization expression, define-values/invoke-unit expands to a define-values expression that binds each identifier export-id to the value of the corresponding variable exported by the unit. At run time, if the unit does not export all of the export-ids, the exn:unit exception is raised.

If prefix is specified, it must be either #f or an identifier. If it is an identifier, the names defined by the expansion of define-values/invoke-unit are prefixed with prefix:.

Example:

(define x 3) 
(define y 2) 
(define-values/invoke-unit (c) 
  (unit (import a b) (export c) 
    (define c (- a b))) 
  ex 
  x yex:c ; => 1 

(namespace-variable-bind/invoke-unit (export-id ···) unit-expr [prefix import-id ···])      SYNTAX

This form is like define-values/invoke-unit, but the expansion is a sequence of calls to namespace-variable-binding instead of a define-values expression. Thus, when it is evaluated, a namespace-variable-bind/invoke-unit expression binds top-level variables in the current namespace.

34.3  Linking Units and Creating Compound Units

The compound-unit form links several units into one new compound unit. In the process, it matches imported variables in each sub-unit either with exported variables of other sub-units or with its own imported variables:

(compound-unit 
  (import variable ···) 
  (link (tag (sub-unit-expr linkage ···)) ···) 
  (export (tag exportage ···) ···)) 
 
linkage is one of 
  variable 
  (tag variable) 
  (tag variable ···) 
 
exportage is one of 
  variable 
  (internal-variable external-variable) 
 
tag is 
  identifier 

The three parts of a compound-unit expression have the following roles:

  • The import clause imports variables into the compound unit. These imported variables are used as imports to the compound unit's sub-units.

  • The link clause specifies how the compound unit is created from sub-units. A unique tag is associated with each sub-unit, which is specified using an arbitrary expression. Following the unit expression, each linkage specifies a variable using the variable form or the (tag variable) form. In the former case, the variable must occur in the import clause of the compound-unit expression; in the latter case, the tag must be defined in the same compound-unit expression. The (tag variable ···) form is a shorthand for multiple adjacent clauses of the second form with the same tag.

  • The export clause re-exports variables from the compound unit that were originally exported from the sub-units. The tag part of each export sub-clause specifies the sub-unit from which the re-exported variable is drawn. The exportages specify the names of variables exported by the sub-unit to be re-exported.

    As in the export clause of the unit form, a re-exported variable can be renamed for external references using the (internal-variable external-variable) form. The internal-variable is used as the name exported by the sub-unit, and external-variable is the name visible outside the compound unit.

The evaluation of a compound-unit expression starts with the evaluation of the link clause's unit expressions (in sequence). For each sub-unit, the number of variables it imports must match the number of linkage specifications that are provided, and each linkage specification is matched to an imported variable by position. Each sub-unit must also export those variables that are specified by the link and export clauses. If, for any sub-unit, the number of imported variables does not agree with the number of linkages provided, the exn:unit exception is raised. If an expected exported variable is missing from a sub-unit for linking to another sub-unit, the exn:unit exception is raised. If an expected export variable is missing for re-export, the exn:unit exception is raised.

The invocation of a compound unit proceeds in two phases to invoke the sub-units. In the first phase, the compound unit resolves the imported variables of sub-units with the bindings provided for the compound unit's imports and new bindings created for sub-unit exports. In the second phase, the internal definitions and expressions of the sub-units are evaluated sequentially according to the order of the sub-units in the link clause. The result of invoking a compound unit is the result from the invocation of the last sub-unit.

Examples

These examples use the definitions from the earlier unit examples in section 34.1.

The following compound-unit expression creates a (probably useless) renaming wrapping around the unit bound to f2@:

(define f3@ 
  (compound-unit 
    (import) 
    (link [A (f2@)]) 
    (export (A (x A:x))))) 

The only difference between f2@ and f3@ is that f2@ exports a variable named x, while f3@ exports a variable named A:x.

The following example shows how the database@ and interface@ units are linked together with a graphical toolbox unit Graphics to produce a single, fully-linked compound unit for the interactive phone book program.

(define program@ 
  (compound-unit  
    (import) 
    (link (GRAPHICS (graphics@)) 
          (DATABASE (database@ (INTERFACE show-message))) 
          (INTERFACE (interface@ (DATABASE insert lookup) 
                                 (GRAPHICS make-window make-button)))) 
    (export))) 

This phone book program is executed with (invoke-unit program@). If (invoke-unit program@) is evaluated a second time, then a new, independent database and window are created.

34.4  Unit Utilities

(unit? v) returns #t if v is a unit or #f otherwise.


11 The detection of unit definitions is the same as for internal definitions (see section 2.8.5 in PLT MzScheme: Language Manual). Thus, the define and define-struct forms can be used for definitions.

12 The ``@'' in the variable name ``f1@'' indicates (by convention) that its value is a unit.