# Units and dependencies

In Dusk, we call a "unit" a collection of words tied to a certain name (the unit
name). For example, the unit "lib/str" provides the word "s)".

In the vast majority of cases, units are tied to a source file of the same name.
For example, the source file associated with unit "lib/str" is /lib/str.fs. Its
associated documentation lives at /doc/lib/str.txt.

A unit is declared with the word "unit". For example, "unit lib/foo" declares
the unit "lib/foo". All following dictionary entries following this declaration
will be associated with the unit "lib/foo". This association stops when the word
"endunit" is called. Following words will be unassociated.

Units cannot be nested. "needs" can only happen outside unit declarations. Both
"unit" and "needs" call "endunit" before doing their thing.

Related to units is also the word "needs", which is used to declare a dependency
to a specific unit. When called, it reads the rest of the line with "readline<"
and then expects a list of unit names sparated by whitespace.

For example, if you type "needs lib/foo lib/bar", you indicate that the code
that is about to be interpreted depends on words from the unit "lib/foo" and
"lib/bar".

As long as [doc/fs/core] is not loaded, "needs" simply asserts that we have the
unit loaded in memory. Once (if) it's loaded, it also automatically loads the
unit from the boot filesystem if it hasn't been loaded yet.

In both cases, when the unit is already loaded in memory, we do something small,
but with far reaching consequences: We "bubble up" all the words that are part
of the needed units on top of the system dictionary (from left to right in the
"" quotes, which means the rightmost unit ends up on top).

This simple little trick allows complex dependency chains to occur while
avoiding word shadowing problems that inevitably crop up as a Forth codebase
becomes wider.

For example, let's say that you loaded "lib/str" earlier, but then created a new
word called "s)" that has completely different semantics that "lib/str"'s "s)".

So far, so good. You can do that and everything will work fine. However, now you
want to load "app/foobar" which depends on "lib/str" and it just so happens that
it needs to use "s)". Guess what? it's going to pick up the wrong "s)" and
"app/foobar" is going to be broken.

But if "app/foobar" calls "needs lib/str", then all those words are going to be
bubbled up to the top in the system dictionary. The words don't actually move,
only the dictionary chain links change (so all compiled references to those
words are still valid, of course). But because "app/foobar" declared that need,
the the "s)" it actually needs will re-shadow the "s)" that shadowed it earlier.

Let's see an example where "lib/a" define a single word, "a", "lib/b", "b" and
"lib/c", "c":

  f<< lib/a
  f<< lib/b
  f<< lib/c
  words \ prints "c b a (... rest of system dict)"
  needs lib/a
  words \ prints "a c b ..."
  needs lib/c lib/b
  words \ prints "b c a ..."

## The two stages of needs

Naively, you could think that:

  needs lib/a lib/b lib/c

and:

  needs lib/a
  needs lib/b
  needs lib/c

are equivalent. You'd *almost* be right. If all libraries are already loaded in
memory, they are equivalent. If one of the libraries is not, then the two forms
are not equivalent because the "bubbling up" that is a side effect of the
loading will mess up our own top level "bubbling up".

For example, if "lib/c" wasn't already in memory and that it depended on
"lib/a", then "lib/a" is going to end up "on top" of "lib/b", which is not the
intent.

It sets the stage for some really tricky bugs and we don't want that. We want a
given namespace following a "needs" to be deterministic.

Therefore, needs operates in two stages. First, it goes through the specified
units and ensure that they're loaded in memory. Then, it goes through the list
again, from left to right, then "bubble up" each unit one after the other.

This ensures a deterministic namespace, independently of prior "loaded" state of
the specified dependencies.

## Handling name clashes

In Dusk's code, we avoid both namespaces and word name prefixes. Each unit
chooses its name for "maximum local elegance", that is, the code should be
understandable locally, without adding spurious prefixes to avoid global name
clashes.

In practice, we find that such clashes rarely happen because those clashing
words are rarely used in the same context.

... but it *can* happen. When that happens, we recommend local aliases. For
example, let's imagine that both "lib/foo" and "lib/bar" have a "xlerb" word.
As long as they're not used together, no problem. They could even be used
together, but only one of the "xlerb" is used, so it's only a matter of
correctly ordering the "needs".

But if we need to use them both, we can use "unitalias":

  needs lib/foo lib/bar
  unitalias lib/foo xlerb fooxlerb
  unitalias lib/bar xlerb barxlerb
  unit app/foobar

One thing that we must remember never to clash with, however, are system words.
These can't be "bubbled up".
