[plt-scheme] unit/sig tutorial
Having spent the last few days frustratedly trying to learn them from
the documentation, I am now writing a short tutorial on how to use
unit/sig basics. Please look at what I've got and give me any
suggestions you have, including a place to post the tutorial when I'm
done with it. This doesn't seem like the cookbook kind of a tutorial to
me, but I could be wrong.
Thanks in advance!
-- Jeff
-----------------------------------------------------------------------
Signed units and functors via unit/sig
Signed units approximate ML 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
complex-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 complex module will invoke without objections:
> (define-values/invoke-unit/sig
(monty-python and-the-holy-grail)
monty-python-and-the-holy-grail@)
Note 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))
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.