Bundle / Bundle and Submodel
Why package a child
The child can change without breaking its parents
A hand-wired parent encodes what the child is today. Suppose the timer
child below gains a Managed Resource. Every parent now needs a
ManagedResource.lift, and a parent that does not get one still compiles: the
resource is never acquired, and nothing says so. Suppose it gains a second
Subscription. A parent that lifted its entries by name, as the example below
has to, never runs the new one.
A placement lifts whatever the child has. A new Subscription arrives through
placements.subscriptions() with no edit to any parent. A new resource arrives
through placements.runtime; in a config checked by placements.complete, a
parent with no managedResources becomes a type error that names the
property. The child's author changes the child, and its users hear about it
from the compiler instead of from a bug report.
A missing piece is a type error, not a quiet bug
Wired by hand, each of these compiles and fails only at runtime, if anyone notices. With a Bundle, each is either impossible or reported:
| Mistake | By hand | Through placements.complete |
|---|---|---|
| A Subscription not lifted | never runs | type error at subscriptions |
| A Managed Resource not lifted | never acquired | type error at managedResources |
A wrapper with no route in update, behind a catch-all branch | its Messages are dropped | no route to write: placements.update routes every wrapper |
| Two placements on one wrapper or one resource tag | Messages misrouted, or one resource replacing the other | startup error naming both |
A second placement is one line, not a second copy
In the example below, the second placement (rest) costs about twenty
lines by hand: a field, a wrapper, its init and Command mapping, a fold, a
route in update, a Subscription lift, a renamed key and an h.submodel. Each repeats the
field name, and the usual way to write them is to copy the first placement
and rename. Miss one rename, say read: model => Option.some(model.work) in
the rest fold, and it compiles, because both fields hold the same type;
the rest timer is then updated from the work timer's state.
With a Bundle the second placement is one Bundle.withChild('rest', Timer)
line. The field name is written once, and the Model field, the wrapper, the
fold, the lifts and the Subscription keys (Timer@rest/ticks) are
all derived from it. Bundle.withEach does the same for a record of children,
one per key.
Configuration and outputs cannot be left out
A child's configuration (args) is a Schema, and every placement must supply
it when init takes it. A child with an OutMessage must be placed with an
onOut, so its outputs cannot be dropped by forgetting to handle them.