Table of Contents
ULAM Programming Conventions
Naming
Utilities vs Services
In the standard library, you can find things like WindowServices
and DebugUtils
, both of which, viewed generally, have similar purposes: They provide methods you can call to do useful things. So, why are some named '*Services' while are named '*Utils'? Here's the key convention: Things suffixed with Utils
should be size 0, while things suffixed with Services
have a non-zero size.
The EventWindow and Random Exceptions
Of course the elephant in the standard library is EventWindow
. That name violates this convention, because it has neither 'Utils' nor 'Services' as a suffix; it gets away with the violation because it predates the whole naming convention. But, since it is size 0, we should think of it like EventWindowUtils
and not
.
EventWindowServices
Exactly the same is true for Random
, which we should think of as RandomUtils
.
Utils and Services Declarations
Rule of Thumb: Prefer data members for *Utils, and local variables for *Services.
Unless you have a specific reason not to, prefer to declare *Utils
(which includes EventWindow
) as data members, and to declare *Services
as local variables in a method. So, for example, prefer this:
- Evaporator.ulam
/** \symmetries all */ element Evaporator { EventWindow ew; Void behave() { ew[0] = ew[1]; } }
over this:
/** \symmetries all */ element Evaporator { Void behave() { EventWindow ew; ew[0] = ew[1]; } }
even though, in this case, both versions behave identically.
The reasoning for this Make *Utils data members guidance has both obvious and subtle points.
An obvious point is that since a *Utils is size 0, it doesn't impact the atomic bit budget at all, so it's harmless to make them data members, while conversely, a *Services data member does impact the bit budget, so we don't want to do it thoughtlessly. Furthermore, placing *Utils as data members makes them available to all methods with a single declaration, and also provides a systematic place (at or near the top of the class) for all *Utils declarations.
A subtle point is that even though a *Utils is size 0, when a *Utils method is called, the self
object passed into the called method can differ depending on how the *Utils was declared. And that can affect how the called *Utils method behaves. For example, this definition:
- Announcer.ulam
element Announcer { DebugUtils du; Void behave() { du.printContext(); } }
produces output like this:
20150526025906-256: 3AEPS [FFE892F7]MSG: @(31, 4) of [1,1]: (An) 20150526025907-257: 8AEPS [FFE892F7]MSG: @(31, 4) of [1,1]: (An) 20150526025907-258: 10AEPS [FFE892F7]MSG: @(31, 4) of [1,1]: (An)
in which the printContext
method reports the event position as well as the symbol (An)
of the self
object it was called on. That's not terribly useful, but consider this very similar definition:
element Announcer { Void behave() { DebugUtils du; du.printContext(); } }
which produces output like this:
20150526030050-256: 2AEPS [FFE91807]MSG: @(17,24) of [2,1]: (E) 20150526030050-257: 3AEPS [FFE91807]MSG: @(17,24) of [2,1]: (E) 20150526030050-258: 4AEPS [FFE91807]MSG: @(17,24) of [2,1]: (E)
What has happened? How can printContext be reporting type (E)
(for Empty) when we know there was an Announcer
atom in the center of the event window – there must have been, or else the Announcer's behave method wouldn't have been called? The answer is that when DebugUtils
is declared as a local variable, it is not associated with the atom at the center of the event window, it is associated with a scratch atom living on the function call stack, that happens to have type Empty.
Many *Utils methods never examine their self
object so this difference is moot. For example, neither EventWindow
nor Random
(both size 0 quarks) do. But some do, and
following the Make *Utils data members ensures that all *Utils method calls will be associated with caller's underlying storage, which is almost always either what you want, or of no harm.