You want to create something like a module, but incomplete, so that some functionality is defined elsewhere and imported at compile-time. What's more, you don't want to worry about circular references, where module a refers to module b which refers to some part of module a.
Signed units are PLT scheme's implementation of
functors, and are essentially composable classes for those people who rest firmly in the OO world; classes with mixins, but a little more flexible than that. They're not intended to replace modules, but rather compliment them. To start using signed units, the first step is to require unitsig.ss.
> (require (lib "unitsig.ss"))
To create a signed unit, the first thing you need is a
signature, which is a single form containing all the symbols that your unit will import or all the symbols your unit will export, or some subset of either of those. So if your unit imports the symbols:
-
graham-chapman
-
john-cleese
-
eric-idle
-
michael-palin
and you want to export the symbols
-
monty-python
-
and-the-holy-grail
Your signatures can look like this
> (define-signature acting-troupe^ (graham-chapman john-cleese eric-idle michael-palin))
> (define-signature silly-movie^ (monty-python and-the-holy-grail))
The circumflex at the end means nothing to
unit/sig -- it's just syntactic sugar that has become a kind of standard convention when working with units. When I said that they can be subsets earlier, I meant I could have split the first signature or second signature into two separate signatures, something along the lines of:
(define-signature bird-watcher^ (michael-palin))
(define-signature Q^ (john-cleese))
(define-signature everyone-else^ (graham-chapman eric-idle))
And it would have been logically the same. The only thing to remember here is to not mix your export and import signatures, as that will be important later. Now, to go about defining that unit.
> (define movie@
(unit/sig silly-movie^
(import acting-troupe^)
(define (monty-python)
(print graham-chapman)
(print john-cleese)
(print eric-idle)
(print michael-palin))
(define (and-the-holy-grail)
(print 'coconuts)
(print 'boring-historian)
(print 'bring-out-your-dead)
(print 'scene-24)
(print 'and-so-on))))
And that's it -- you have a signed unit. It's incomplete, though, because we haven't defined our actors yet. These are actually either defined as a separate unit or defined at the top level. Let's take the case where they're defined as a separate unit, though, because this is effectively a
functor, which is a very tidy way of composing programs from components.
> (define actors@
(unit/sig acting-troupe^
(import)
(define graham-chapman 'arthur)
(define michael-palin 'galahad)
(define john-cleese 'lancelot)
(define eric-idle 'robin)))
Not bad. Note the differences with the
movie@ unit. Most importantly, import is completely bare, which means that no other symbols are needed to make
actors@ complete. Note also that every symbol specified in the
acting-troupe^ signature is defined here in the module. So let's do the obvious thing:
> (invoke-unit/sig actors@)
> (invoke-unit/sig movie@ acting-troupe^)
Eek! That produced an error that our symbols were undefined. Let's look at why. Invoking units with
invoke-unit or
invoke-unit/sig does not define anything at the top level. Exports are only meant to be exported to other units. There is a function
(define-values/invoke-unit/sig) which when used in place of
invoke-unit/sig will define the symbols from
actors@ -- all the symbols needed for
acting-troupe at the top level, but that's 'messy' on the namespace. A more proper solution to this problem is to define a
compound-unit/sig:
> (define monty-python-and-the-holy-grail@
(compound-unit/sig silly-movie^
(import)
(link (A : acting-troupe^ (actors))
(B : silly-movie^ (movie@ A)))
(export (open B))))
This new
compound unit will invoke without objections:
> (define-values/invoke-unit/sig
(monty-python and-the-holy-grail)
monty-python-and-the-holy-grail@)
Note that we use the define-values variant to make sure that the symbols from
movie@ are defined at the top level at the end of the invocation. There is another way to do things and that is to realize that a unit, signed or not, is a function and should return a value. Thus, changing our unit slightly:
> (define movie@
(unit/sig silly-movie^
(import acting-troupe^)
(define (monty-python)
(print graham-chapman)
(print john-cleese)
(print eric-idle)
(print michael-palin))
(define (and-the-holy-grail)
(print 'coconuts)
(print 'boring-historian)
(print 'bring-out-your-dead)
(print 'scene-24)
(print 'and-so-on))
and-the-holy-grail))
Doing things this way, we can control whether or not and what the symbol for
and-the-holy-grail is at the top level. Now calling
invoke-unit/sig will return a
<#procedure> instead of void, which you can bind or immediately apply.
Take note that this only describes the basic features of signed units, and that there is considerably more functionality embedded in unitsig.ss. From here you can begin coding component software with an understanding of what a unit is, what a signature is, and why to bother with a compound-unit. After having read this tutorial, you should be able to gradually add the more esoteric functionality of unit/sig to your repertoire by reading the
MzLib? documentation and experimenting with the functions within.
--
JeffHeard - 18 Aug 2006