14.3 Linking Units

We can make our toy economy more efficient by having toy factories that cooperate with stores, creating toys that do not have to be repainted. Instead, the toys are always created using the store’s color, which the factory gets by importing toy-store^:

"store-specific-factory-unit.rkt"

#lang racket
 
(require "toy-store-sig.rkt"
         "toy-factory-sig.rkt")
 
(define-unit [email protected]
  (import toy-store^)
  (export toy-factory^)
 
  (define-struct toy () #:transparent)
 
  (define (toy-color t) (store-color))
 
  (define (build-toys n)
    (for/list ([i (in-range n)])
      (make-toy)))
 
  (define (repaint t col)
    (error "cannot repaint")))
 
(provide [email protected])

To invoke [email protected], we need toy-store^ bindings to supply to the unit. But to get toy-store^ bindings by invoking [email protected], we will need a toy factory! The unit implementations are mutually dependent, and we cannot invoke either before the other.

The solution is to link the units together, and then we can invoke the combined units. The define-compound-unit/infer form links any number of units to form a combined unit. It can propagate imports and exports from the linked units, and it can satisfy each unit’s imports using the exports of other linked units.

> (require "toy-factory-sig.rkt")
> (require "toy-store-sig.rkt")
> (require "store-specific-factory-unit.rkt")
> (define-compound-unit/infer [email protected]
    (import)
    (export toy-factory^ toy-store^)
    (link [email protected]
          [email protected]))

The overall result above is a unit [email protected] that exports both toy-factory^ and toy-store^. The connection between [email protected] and [email protected] is inferred from the signatures that each imports and exports.

This unit has no imports, so we can always invoke it:

> (define-values/invoke-unit/infer [email protected])
> (stock! 2)
> (get-inventory)

(list (toy) (toy))

> (map toy-color (get-inventory))

'(green green)