[plt-scheme] unit/sig tutorial

From: Jeff Heard (heard at duvel.ir.iit.edu)
Date: Fri Aug 11 18:25:46 EDT 2006

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.


Posted on the users mailing list.