Invoking Units: Every Which Way …

Units in PLT Scheme offer great promise in the area of parameterizing modular code. Rather than lean on require forms (or imports in other languages) for bringing identifier bindings into scope, Units offer programmatic parameterization. Instead of editing the source code of a module that depends on some other code, one can provide that parameter at, or close to, the ultimate use site of the fully realized Unit (perhaps in a module dedicated specifically to wiring together the components of an application). However, the reality isn’t always characterized by blue skies and smooth sailing. One area that has often bothered me is that if I create a Unit foo that depends on some unit exporting a signature setup^ (i.e. foo’s setup), the process of invoking foo can be somewhat cumbersome.

More specifically, I might have some unit g that exports the setup^ signature, so how do I go about invoking foo with g as a parameter? It sounds like I want something that looks like (foo g), but perhaps that’s not quite right since we often want the invocation of a Unit to add bindings to the caller’s lexical context. This explains why Unit invocation is actually done by forms such as define-values/invoke-unit or define-values/invoke-unit/infer. An emerging theme is that these are rather long names! To make matters worse, the /infer variants of Unit functions require that argument identifiers be bound by forms like define-unit. But that is a serious limitation, as illustrated in an earlier post.

If we set aside the /infer functionality, then define-values/invoke-unit itself leaves a bit to be desired since it requires explicit import and export annotations. But before we can even call define-values/invoke-unit we must link the Unit exporting setup^ to the Unit importing it, in our case, foo. This requires the creation of a compound-unit that specifies the necessary linking. Unfortunately, while that syntax is powerfully expressive, it is also somewhat scary compared to the initial hope for invocation syntax looking like (foo g).

To this end, I have written up several options for our example Unit invocation scenario. Each option may be uncommented (one at a time), and the entire module evaluated to see that bar is appropriately bound. The idea in these examples is that any helper functionality can be put into another module, while each Option represents a possible call-site syntax. The in-line unit expressions are also likely to be pulled in from elsewhere (e.g. one may have several back-end service providers expressed as Units that can be supplied to a client of any service exporting a particular signature).

#lang scheme

(define-signature setup^ (baz))
(define-signature foo^ (bar))
(define-unit foo@
  (import setup^)
  (export foo^)
  (define bar (baz 2)))

;; Option 1
;; Put the setup requirements into the current namespace so that the 
;; import binding can be inferred from context.
;(define (baz x) (+ x 40))
;(define-values/invoke-unit/infer foo@)

;; Option 2
;; Manually create a unit to provide setup^ and link it to foo@ before 
;; invoking the compound unit to get the desired bar binding.
;  (compound-unit
;    (import)
;    (export Foo)
;    (link (((Setup : setup^)) (unit (import) 
;                                    (export setup^)
;                                    (define (baz x) (+ x 40))))
;          (((Foo : foo^)) foo@ Setup)))
;  (import)
;  (export foo^))

;; Use a helper function to do the linking. The helper function can live in 
;; another module.
(define (init-setup setup)
    (export Foo)
    (link (((Setup : setup^)) setup)
          (((Foo : foo^)) foo@ Setup))))

;; Option 3
;; Use the helper function with an explicit define-values/invoke-unit. 
;; This has the benefit of hiding the linking.
;  (init-setup (unit (import) (export setup^) (define (baz x) (+ x 40))))
;  (import)
;  (export foo^))

;; A helper macro that can reduce the call site burden to knowing how to 
;; provide the necessary setup^ bindings, but requires neither an empty 
;; (import) nor a mention of the foo^ signature. Thanks to Jon Rafkind.
(define-syntax (mkfoo stx)
  (syntax-case stx ()
    [(_ setup) (syntax-local-introduce
                    (init-setup setup)
                    (export foo^)))]))

;; Option 4
;; Use a macro to completely hide the requirements of using the compound 
;; unit.
;(mkfoo (unit (import) (export setup^) (define (baz x) (+ x 40))))

;; Get the caller's lexical context for the generated bindings by using 
;; a provided export signature. Thanks to Matthew Flatt.
(define-syntax mkfoo2
  (syntax-rules ()
    [(_ setup sig) (define-values/invoke-unit
                     (init-setup setup)
                     (export sig))]))

;; Option 5
;; Provide the export signature from the caller's context so that bindings 
;; are made in the right context.
;(mkfoo2 (unit (import) (export setup^) (define (baz x) (+ x 40))) foo^)

; Make sure we have a binding for bar!

2 thoughts on “Invoking Units: Every Which Way …

Comments are closed.