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:

MistakeBy handThrough placements.complete
A Subscription not liftednever runstype error at subscriptions
A Managed Resource not liftednever acquiredtype error at managedResources
A wrapper with no route in update, behind a catch-all branchits Messages are droppedno route to write: placements.update routes every wrapper
Two placements on one wrapper or one resource tagMessages misrouted, or one resource replacing the otherstartup 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.