Notes on Units ============== (Danny Yoo ) This is a set of notes that I'm keeping for myself as I read about the unit system in MzScheme. Some of this material may be outright wrong: I'm hoping that folks who know better will help correct me. There's so little introductory material on Units, so I hope that this helps to fill a need. If anything, explaining it to myself will at least force me to really understand it. I should mention up front that there's another set of notes by Matthias Felleisen and Matthew Flatt here: http://www.ccs.neu.edu/home/matthias/Tmp/Units which provide more of a high-level overview of units --- some of the material is slightly outdated, but the core material there is good. We'll assume for the moment that we already have a working idea of how modules work in MzScheme. That is, something like (module hello-module mzscheme (provide (all-defined)) (define (hello-world) (display "hello world!") (newline))) should not look too scary: *hello-module* is a module that provides a single function called *hello-world*. And other people can use *hello-module* by saying something like: > (require (prefix m: hello-module)) > (m:hello-world) hello world! to produce a friendly greeting on screen. Of course, we know that modules aren't useful if they provide only a single function: they're usually used to package up larger batches of code. But it's easy to do demonstrations with toy examples, so these notes will be filled with toys. In fact, let's start with a "hello world" example with units. First, let's pull on MzScheme's standard library to get unit support installed: > (require (lib "unit.ss")) Ok, now our system has support for units. Let's see what a unit looks like: > (define hello-world-unit (unit (import) (export hello-world) (define (hello-world) (display "hello world, I come from a unit") (newline)))) > > hello-world-unit # Now we have a "unit", whatever this "unit" thing is. How do we use it? > (define-values/invoke-unit (hello-world) hello-world-unit) > hello-world # > (hello-world) hello world, I come from a unit That wasn't too bad: units look just like modules. To use a module, we use the *require* form; to use a unit, we use an *invoke* form like *define-values/invoke-unit. Aside: unit syntax error messages can be greatly improved --------------------------------------------------------- [I'm hoping this set of notes will deprecate quickly as the PLT folks fix this.] The syntax is a little funny and slightly more verbose than modules. Let's intentionally try a few things that we know will break, and see if the system is nice enough to tell us how to fix things. > (unit) repl-7:1:0: unit: bad syntax in: (unit) > (unit (export)) repl-8:1:0: unit: bad syntax in: (unit (export)) > (unit (import)) repl-9:1:0: unit: bad syntax in: (unit (import)) > (unit (export) (import)) repl-11:1:0: unit: bad syntax in: (unit (export) (import)) > (unit (import) (export)) # Well, that's good to know. Ordering matters a lot, as well as including everything that a unit expects. MzScheme's unit syntax errors are a bit unhelpful. We need to make sure we define what a unit's *import*s and *export*s are; if we forget them, we'll get a inscrutable error. Of course this is not fundamental to units, but it's a good thing to be careful about. One other thing to be careful about is to remember to bring in unit syntax by doing the requisite: (require (lib "unit.ss")) Terrible syntax errors show up if we don't: ;;; In a mzscheme session where we haven't ;;; yet done a (require (lib "unit.ss")): > (define hello-world-unit (unit (import) (export hello-world) (define (hello-world) (display "hello world, I come from a unit") (newline)))) repl-2:5:11: define: not allowed in an expression context in: (define (hello-world) (display "hello world, I come from a unit") (newline)) The main issue here is that the error messages that come out will sometimes point at the wrong thing: the error message here yells about the misplaced *define*, when it would be much more helpful to know that mzscheme doesn't yet know about *unit*, as in: > (unit (import) (export)) reference to undefined identifier: unit Unfortunately, this is something that might be hard to fix in mzscheme. So if we see an error that looks like the above, then we should double check that we haven't forgotten to do a (require (lib "unit.ss")). Back to playing with units -------------------------- Let's try another toy example: > (define (make-counting-unit) (let ((n 0)) (unit (import) (export say-count) (define (say-count) (display n) (newline) (set! n (+ n 1)))))) > (define c1 (make-counting-unit)) > (define c2 (make-counting-unit)) > (define-values/invoke-unit (say-count) c1) > (say-count) 0 > (say-count) 1 > (say-count) 2 > (say-count) 3 > (define-values/invoke-unit (say-count) c2) > (say-count) 0 > (say-count) 1 > (define-values/invoke-unit (say-count) c1) > (say-count) 4 So that's interesting: since units are first class, they can be returned from functions. And like anything else in Scheme, they can take advantage of lexical scope to maintain memory. Let's try something slightly different: > (define counting-unit-2 (unit (import) (export say-count) (define n 0) (define (say-count) (display n) (newline) (set! n (+ n 1))))) > (define-values/invoke-unit (say-count) counting-unit-2) > (say-count) 0 > (say-count) 1 > (say-count) 2 > (define-values/invoke-unit (say-count) counting-unit-2) > (say-count) 0 This, too, shows memory, but when we reinvoke the unit, something happens: we've reevaluated the unit's definitions. When we *invoke* a unit, we cause the unit body definitions to fire off again. In some sense, unit invokation is like the "instantiation" of a class in other programming languages. What do we see different between units and modules so far? ---------------------------------------------------------- o Terminology ( unit <--> module, import <--> require, export <--> provide ) o Units are first class: they can be passed around like other objects in Scheme. o Units can be invoked over and over: we can have several invokations of the same unit existing in the same program. This contrasts with modules: modules normally initialize only once. That if, if we do: (module foo mzscheme (display "hey, look at me") (newline)) (require foo) (require foo) we only expect to see our message once, since mzscheme knows that we've already required the module. Units can be invoked without caring about the exports ----------------------------------------------------- In fact, because units can be invoked over and over, there's actually a value in writing units that don't necessarily have definitions --- we can treat units as whole programs, by using the *invoke-unit* special form: > (define simple-hello-unit (unit (import) (export) (display "hello world") (newline))) > (invoke-unit simple-hello-unit) hello world > (invoke-unit simple-hello-unit) hello world Furthermore, because we can treat units like small bundled programs, we can get a value out of invoking them: > (invoke-unit (unit (import) (export) (+ 1 1))) 2 Again, this example is only meant to be a toy: there is no way in heck the *one-plus-one-unit* here can be remotely useful. But at least it also shows that it's really easy to build simple units on the fly. But, even so, why would we want units? -------------------------------------- But at the moment, doesn't seem yet that units buy us much yet compared to modules. But let's look at a part of the unit system that we've been completely ignoring so far --- *import*. We can design units so that part of their definition comes from the outside world. Let's take a simple example: > (define adder-unit (unit (import n) (export adder) (define (adder x) (+ x n)))) > adder-unit # > (invoke-unit adder-unit) invoke-unit: expected a unit with 0 imports, given one with 1 imports We now have a unit that's can't be directly invoked. Is this a step backwards? Let's figure out how to invoke a unit with a declared *import*. adder-unit says that it needs an n --- let's give it that. > (define 5-unit (unit (import) (export n) (define n 42))) (define 5-unit (unit (import) (export n) (define n 42))) We now have two units: *adder-unit* imports an *n*, and 5-unit exports an *n*, so the next step is inevitable: let's glue them together somehow. That "somehow" uses the *compound-unit* special form. > (define 5-adder-unit (compound-unit (import) (link [ADDER-TAG (adder-unit (N-PROVIDER-TAG n))] [N-PROVIDER-TAG (5-unit)]) (export (ADDER-TAG adder)))) [Note to self: add gripe on how miserable the error messages are in building correct compound-units too, or send patch in svn to make the syntax errors more tolerable...] Let's see what this *compound-unit* thing has done before we go on: > 5-adder-unit # > (invoke-unit 5-adder-unit) > Ok, at least there's progress: *5-adder-unit* is invokable now, and is itself a unit that we built by *linking* units together. We know what the *adder* in 5-adder-unit is supposed to do, but let's just double check it to ease our fears --- let's use *define-values/invoke-unit* and see what that exportable *adder* looks like now: > (define-values/invoke-unit (adder) 5-adder-unit) > (adder 5) 47 Ok, it does work. Let's look back and what we've done. Actually, let's go back and make this a little more general: > (define adder-unit (unit (import n) (export adder) (define (adder x) (+ x n)))) > (define (make-adder-unit k) (let ((n-provider (unit (import) (export n) (define n k)))) (compound-unit (import) (link [A (adder-unit (N n))] [N (n-provider)]) (export (A adder))))) > (define-values/invoke-unit (adder) (make-adder-unit 7)) > (adder 42) 49 If we look at this critically, we might think that all this is a bit verbose. Can't we do the same thing with higher-order functions? Yes, of course we can: > (define (make-adder n) (define (adder x) (+ x n)) adder) So whatever advantage units provide better be good, because they are a bit syntactially heavyweight. Units are parameterized modules ------------------------------- Let's try another example. Many of us are familiar with binary search trees as a data structure: > (define (make-node key left right) (list key left right)) > (define (node-key node) (list-ref node 0)) > (define (node-left node) (list-ref node 1)) > (define (node-right node) (list-ref node 2)) Here, we'll be informal and say that a tree can either be the empty list or a constructed node. Let's continue by defining the *insert* and *search* operations: ;; inserts k into the tree > (define (insert tree k) (cond ((null? tree) (make-node k '() '())) ((< k (node-key tree)) (make-node (node-key tree) (insert (node-left tree) k) (node-right tree))) (else (make-node (node-key tree) (node-left tree) (insert (node-right tree) k))))) ;;; find the first node whose key is k > (define (search tree k) (cond ((null? tree) (error 'search "Can't find ~s" k)) ((= k (node-key tree)) tree) ((< k (node-key tree)) (search (node-left tree) k)) (else (search (node-right tree) k)))) (Some of that code there could be nicer if I used real structures and the pattern-matching tools in plt-match.ss, but we can leave that for later.) Let's stop for a moment and notice: this implementation only works for numeric keys because we use the comparison operators *<* and *=*. We could use higher-order functions to make this more general. But another approach we can take is to use a unit that depends on those two operations: > (define insert&search-unit (unit (import < =) (export insert search) (define (insert tree k) (cond ((null? tree) (make-node k '() '())) ((< k (node-key tree)) (make-node (node-key tree) (insert (node-left tree) k) (node-right tree))) (else (make-node (node-key tree) (node-left tree) (insert (node-right tree) k))))) (define (search tree k) (cond ((null? tree) (error 'search "Can't find ~s" k)) ((= k (node-key tree)) tree) ((less-than k (node-key tree)) (search (node-left tree) k)) (else (search (node-right tree) k)))))) And once we have this, now we can start using it: > (define-values/invoke-unit (search insert) insert&search-unit #f string (insert () "foo") ("foo" () ()) > (insert (insert () "foo") "bar") ("foo" ("bar" () ()) ()) > (insert (insert (insert () "foo") "bar") "baz") ("foo" ("bar" () ("baz" () ())) ()) We now have a generalized set of binary tree operations that we can adapt to anything that provides those comparison operators. In short, we've defined a parameterized module. I skipped a few steps in invoking the unit, by directly invoking a unit with imports remaining. *define-values/invoke-unit* can take in an optional list of import ids if the linkage is particularly simple, and I chose to use the alternative form because it was easier to type. (The *#f* false symbol as the third argument to *define-values/invoke-unit* is there to disable prefix designation; we'll touch on this at the end of this section.) If we were to avoid the shortcut, to do this with a compound unit, things might look a bit wordier: > (define string-comparisons-unit (unit (import) (export lt eq) (define-values (lt eq) (values string (define string-insert&search-unit (compound-unit (import) (link [S (string-comparisons-unit)] [T (insert&search-unit (S lt) (S eq))]) (export (T insert) (T search)))) > (define-values/invoke-unit (insert search) string-insert&search-unit) > > (insert () "foo") ("foo" () ()) It's conceptually simple, but again, a bit verbose. In simple cases like the above, it can be easier to just feed the imports directly when we invoke a unit. But in some cases, the linkage is complicated enough that we can't do provide thing inlined with the invokation. (In the next section, we'll play with a concrete example that shows when the explicit linkages can't be avoided.) Finally, one thing we might be sad about is being forced to have 'insert' and 'search' be either the insert and search for numbers, or the insert and search for strings. Is there any way we can have both? That is, how do we avoid namespace conflict? If we were using modules, we might consider using the *prefix* form to get us out of trouble. For example: > (define second "2nd") > (require (prefix l: (lib "list.ss"))) > l:second # > second "2nd" shows that we pull all the definitions exported by the *list* module, but prefix all the symbols with 'l:' to make namespace conflict unlikely. As we might expect, the unit system has this feature too; it allows us to "prefix" exported identifiers to avoid namespace conflict: > (define-values/invoke-unit (insert search) insert&search-unit s string (define-values/invoke-unit (insert search) insert&search-unit n < =) > > s:insert # > n:insert # And now we have two versions of the *insert* function, one specialized for strings, and the other specialized for numbers. When we're invoking a unit, we can provide a *prefix* that prepends that prefix on every exported symbol of the unit. Prefixing comes in handy in cases where we might want to reuse a unit in different contexts. Units allow for mutual dependencies ---------------------------------- Let's start by borrowing the classic "even/odd" toy example: > (define-values (even? odd?) (letrec ((e? (lambda (x) (if (= x 0) #t (o? (- x 1))))) (o? (lambda (x) (if (= x 0) #f (e? (- x 1)))))) (values e? o?))) Most of us have seen this before as a motivating example for letrec. Letrec allows us to define both even? and odd?, even though they depend on each other. Given this, it might be a natural question (... or perhaps not? *grin*) to wonder: can we define these two functions in separate modules? But we know this doesn't work, because mzscheme modules have to be arranged in an acyclic graph: no loopiness allowed. And that's where units can come in: units can be arranged in loopy graphs. Let's first start by defining two units: > (define even-unit (unit (import odd?) (export even?) (define (even? x) (if (= x 0) #t (odd? (- x 1)))))) > (define odd-unit (unit (import even?) (export odd?) (define (odd? x) (if (= x 0) #f (even? (- x 1)))))) Note that these two units can themselves live in physically separate modules. We don't run into circularity problems because by the time we do try using even-unit or odd-unit, we will link them together with a compound-unit first. > (define even-and-odd-unit (compound-unit (import) (link (E (even-unit (O odd?))) (O (odd-unit (E even?)))) (export (E even?) (O odd?)))) > (define-values/invoke-unit (even? odd?) even-and-odd-unit) And now we can use even? and odd?, and the world is a happy place again. The slightly ugly *link* definition in our compound unit is the key to this, so let's concentrate on the general shape of it first. (link (E ...) (O ...)) This says that we'll be combining two units --- let's call them E and O for the moment. Now let's concentrating on E's linkage definition: (link (E (even-unit (O odd?))) (O ...)) We see that E is actually an instantiation of our even-unit. But of course, we can't just use even-unit: it requires a good definition of odd? to work. But since E imports a definition of odd?, we can provide that by connecting it to O's odd? exported definition. And vica-versa, O is an instantation of the odd-unit, where we need to connect O's import of even? to the definition provided by E. So that's how we come up with the link declaration: (link (E (even-unit (O odd?))) (O (odd-unit (E even?)))) Extended example: number guessing game -------------------------------------- Let's try another example to get more familiar with units. For this example, we'll use the toy "Guess the number" game, where we pit our enormous numerical prowess to figure out what number another person is thinking of. One main thing that we'd like to do is be able to plug in different players to our game --- that'd give us the chance to write both mechanical and interactive clients. First up is our definition of the number game: (define (make-game-unit secret max-guesses) (unit (import play) (export guess! guesses-left) ;; has the player guessed the secret number? (define guessed? #f) ;; how many guesses has the player taken so far? (define guesses-so-far 0) ;; did the player make too many guesses? (define (too-many-guesses?) (> guesses-so-far max-guesses)) ;; Returns the number of guesses left till loss. (define (guesses-left) (- max-guesses guesses-so-far)) ;; Make another guess. ;; ;; Returns (union 'too-many-guesses 'greater 'smaller 'equal). (define (guess! n) (printf "DEBUG: guess=~s~n" n) (set! guesses-so-far (+ 1 guesses-so-far)) (cond ((too-many-guesses?) 'too-many-guesses) ((> n secret) 'greater) ((< n secret) 'smaller) (else (set! guessed? #t) 'equal))) ;; finally, really play the game. The result should be if ;; the user guessed? or not within the number of guesses. (begin (play) guessed?))) We now have a game unit (actually, a function that constructs game units) that just needs to be linked up with a player. Before we continue, we should point out two things: 1. We've designed this program so that there is a circular dependency between the game and the players. Players provide a *play* function, and we anticipate that players themselves will be telling the game what they *guess!*, and might also ask how many *guesses-left* they have. 2. When we play the game, the final value of the game is the value *guessed?*, which tells us if the player won or not. Oh, also, let's make a function to do the linkage for us, given a game and a player unit. (define (play-game game-unit player-unit) (let ((complete-game-unit (compound-unit (import) (link [P (player-unit (G guess!) (G guesses-left))] [G (game-unit (P play))]) (export)))) (invoke-unit complete-game-unit))) Finally, let's see what two sample players might look like. First, a mechanically stupid one: (define brute-force-player-unit (unit (import guess! guesses-left) (export play) (define (play) (let loop ((current-guess 0)) (case (guess! current-guess) [(too-many-guesses) 'darn] [(greater) (loop (- current-guess 1))] [(smaller) (loop (+ current-guess 1))] [(equal) 'yahoo]))))) as well as a perhaps not-so-stupid one: ourselves! *grin* (define simple-interactive-player-unit (unit (import guess! guesses-left) (export play) (define (prompt) (printf "~s guesses left. guess, or q to quit? " (guesses-left)) (read)) (define (play) (let ((current-guess (prompt))) (when (not (equal? current-guess 'q)) (case (guess! current-guess) [(too-many-guesses) (printf "too many guesses. too bad~n")] [(greater) (printf "too big~n") (play)] [(smaller) (printf "too small~n") (play)] [(equal) (printf "you got it!~n")])))))) Let's try this out and see if it actually works: > (play-game (make-game-unit -7 10) brute-force-player-unit) (play-game (make-game-unit -7 10) brute-force-player-unit) DEBUG: guess=0 DEBUG: guess=-1 DEBUG: guess=-2 DEBUG: guess=-3 DEBUG: guess=-4 DEBUG: guess=-5 DEBUG: guess=-6 DEBUG: guess=-7 #t > > (play-game (make-game-unit 42 10) brute-force-player-unit) (play-game (make-game-unit 42 10) brute-force-player-unit) DEBUG: guess=0 DEBUG: guess=1 DEBUG: guess=2 DEBUG: guess=3 DEBUG: guess=4 DEBUG: guess=5 DEBUG: guess=6 DEBUG: guess=7 DEBUG: guess=8 DEBUG: guess=9 DEBUG: guess=10 #f > > (play-game (make-game-unit 42 10) simple-interactive-player-unit) 10 guesses left. guess, or q to quit? 5 DEBUG: guess=5 too small 9 guesses left. guess, or q to quit? 100 DEBUG: guess=100 too big 8 guesses left. guess, or q to quit? 42 DEBUG: guess=42 you got it! #t Yah; it works! Let's go back for a moment, and look at the linkage definition in *play-game*: (link [P (player-unit (G guess!) (G guesses-left))] [G (game-unit (P play))]) What happens if we reverse the order of the linkage, to something like this? (define (play-game-broken game-unit player-unit) (let ((complete-game-unit (compound-unit (import) (link [G (game-unit (P play))] [P (player-unit (G guess!) (G guesses-left))]) (export)))) (invoke-unit complete-game-unit))) Of course this is a loaded question. *grin* But let's see it just to be sure what effect *play-game-broken* has: > (play-game-broken (make-game-unit -7 10) brute-force-player-unit) procedure application: expected procedure, given: # (no arguments) There's a gotcha! The problem is that the order of linkage affects how things evaluate when the compounded complete-game-unit is actually invoked. This time, because we define G before P, the toplevel expressions of the game-unit G evaluate first. And when we try to do: (begin (play) guessed?))) we haven't yet hit the linkages for the player-unit P, so *play* hasn't been initialized to its correct value yet. In this example, because we treat our game-unit as the main entry point that starts up the evaluation of everything else, we should link it last, or else bad things happen. Signed Units ------------ Now that we have some concrete experience with units, let's look at a possible "oops!" that can happen, and then show what we can do to avoid the oops. > (define my-math-unit (unit (import) (export add sub mul div) (define (add x y) (+ x y)) (define (sub x y) (- x y)) (define (mul x y) (* x y)) (define (div x y) (/ x y)))) > (define (test-equal? x y) (unless (equal? x y) (error 'test-equal? "~a not equal to ~a" x y))) > (define test-unit (unit (import sub mul add div) (export) (test-equal? 7 (add 3 4)) (test-equal? 2 (sub 5 3)) (test-equal? 72 (mul 9 8)) (test-equal? 42 (div 84 2)) 'all-good)) > (define (do-the-test math-unit) (invoke-unit (compound-unit (import) (link (MATH (my-math-unit)) (TEST (test-unit (MATH add sub mul div)))) (export)))) > (do-the-test my-math-unit) test-equal?: 7 not equal to 12 The error here is trivial: we've mistakenly misordered the import list in our test-unit: (define test-unit (unit (import sub mul add div) ...)) And we know now how to fix this particular bug. (Of course we do: we just cooked up this toy example! *grin*) We can just reorder things to make it work. But this still highlights a serious scaling problem: the unit's imports and exports are positional! And it's all too easy to misorder these things. This problem gets more painful as the set of imported variables gets large, as we know that it will in real-life systems. It'd be better if unit linkage depended on names instead of positions. And that's where "signatures" come in. Let's show how signatures can make it more difficult to mess up linkage. First, we need to load the support module up: > (require (lib "unitsig.ss")) Going back to our math example, we see that the test-unit worked with something that provided math primitives. What are those math primitives? > (define-signature math-primitives-sig (add sub mul div)) Any unit that provides math primitives should satisfy math-primitives-sig. What does my-math-unit look like if we use signatures? > (define my-signed-math-unit (unit/sig math-primitives-sig (import) (define (add x y) (+ x y)) (define (sub x y) (- x y)) (define (mul x y) (* x y)) (define (div x y) (/ x y)))) Our new my-signed-math-unit declares itself to provide the things in math-primitives-sig. And if we compare this against our original my-math-unit, we see that the export list is now absorbed into the signature. ---------------------------------------------------------------------- (There's a small gotcha: the position of the exports becomes the first argument to the unit/sig special form: (unit/sig math-primitives-sig ...) This is different from what we saw earlier with unit: (unit (import) (export ...) ...) where the "exports" are the second argument to the unit form. This is just a slight syntactic inconsistancy to keep in mind.) ---------------------------------------------------------------------- Anyway, coming back to our example: but what happens if we miss something? > (define broken-math-unit (unit/sig math-primitives-sig (import) (define (add x y) (+ x y)))) repl-15:2:8: unit/sig: signature "math-primitives-sig" requires variable "div" in: (unit/sig math-primitives-sig (import) (define (add x y) (+ x y))) Ok, good: unit/sig detects if we've been silly and forgotten to implement everything that the signature promises. Let's see what test-unit looks like with signatures: > (define test-signed-unit (unit/sig () (import math-primitives-sig) (test-equal? 7 (add 3 4)) (test-equal? 2 (sub 5 3)) (test-equal? 72 (mul 9 8)) (test-equal? 42 (div 84 2)) 'all-good)) Pretty much the same as before, except things are a little shorter. What might be a little weird is the empty signature '() that we attach to test-signed-unit, but that just means that test-signed-unit doesn't have an interesting export signature. Finally, let's try this all out! > (define (do-the-signed-test signed-math-unit) (invoke-unit/sig (compound-unit/sig (import) (link (MATH : math-primitives-sig (signed-math-unit)) (TEST : () (test-signed-unit MATH))) (export)))) > (do-the-signed-test my-signed-math-unit) all-good > And it's all good. How do we analogize them to features in other languages? -------------------------------------------------------- [Show functors in ML] [Show how, in many OOP systems, we often use classes to do ad-hoc dynamic linking between components. The unit system is a more systematic way to do the linking.] Where are units used in PLT Scheme? ----------------------------------- [Work out example with PLT servlets] [Work out example with the teachpack system] Can we see a real, worked-out example that justifies learning them? ------------------------------------------------------------------- [fill me in: example of dynamic linking with plugins] Tricks of the trade: compound-unit can do renaming -------------------------------------------------- [fill me in] Where can I learn more? ----------------------- Matthias Felleisen recommends: http://www.ccs.neu.edu/home/matthias/Tmp/SML/index.html for notes on component-based software development. He's also written another set of notes specifically for units here: http://www.ccs.neu.edu/home/matthias/Tmp/Units [Put references to papers that have useful information on units, especially Modular Object-Oriented Programming with Units and Mixins at: http://www.cs.utah.edu/plt/publications/icfp98-ff/paper.shtml. It's bizarre that the reference documentation doesn't even link to the paper or at least footnotes it, since it basically gives a nice unit tutorial in there...]