Designing the Field Component

Designing the Property Type Component and Designing the Module Component both called for a mechanism to handle fields.

What is the Field Component?

Modules are meant to be black-box functions which take all the input (inputs and callbacks) they will need to compute their results. The inputs and results are the fields. Since callbacks are passed as inputs we consider how they are passed to be part of the field component, while the actual callback mechanism falls to the call graph component (see Designing the Call Graph Component).

Field Component Considerations

  1. Work with arbitrary types.

    • Downstream users of PluginPlay should be able to use their native types.

  2. Recognize differences between inputs and results.

    • Inputs can be by value or by read-only reference.

    • Results are always by value.

    • Note can move into a value argument.

  3. Record provenance of inputs/results.

    • The inputs/results should hold metadata to sufficiently describe them.

    • Metadata can conceivably be used by callers of the module as a “tooltip”

  4. Verify inputs satisfy bound constraints.

    • Associating checks with the field avoids waiting until the module runs to ensure the field’s value is valid.

    • Used to prevent users from setting a field to an incorrect value (before the module tries to unwrap the input).

  5. Leverage metadata to generate documentation.

    • Keeping documentation up to date with API changes can be tedious.

    • Generating documentation from metadata keeps code and documentation synched.

Field Component Design

../../_images/field_design.png

Fig. 9 The three major components of PluginPlay’s field component. ModuleInput/ ModuleResult are responsible for respectively managing the inputs/results to/from a module and SubmoduleRequest is used to manage callback hooks.

Fig. 9 shows the architecture of the field component. The three major components, ModuleInput, ModuleResult, and SubmoduleRequest correspond to three major items passed into/from modules. ModuleInput manages an input to a module (the set of inputs passed to a module will be a container of ModuleInput objects). In addition to the type-erased input, which lives in the AnyField object, the ModuleInput also holds metadata (e.g., a description, if the user set the value, is the value Opaque) and any bounds checks (e.g., can the value be null, is there a maximum/minimum for the value, what are the allowed strings). The ModuleResult is similar except it does not have bounds checks (the module developer presumably is not trying to return invalid results). The SubmoduleRequest class is designed to hold a callback hook, and the callback once it is set.

Summary

  1. Work with arbitrary types.

    • AnyField uses type-erasure to wrap whatever type the module developer and/or property type developer wants.

    • Types live in the module implementation or in the property type, both of which live downstream from PluginPlay.

  2. Recognize differences between inputs and results.

    • ModuleInput and ModuleResult are different classes to better capture this point.

  3. Record provenance of inputs/results.

    • All classes have metadata to record provenance.

  4. Verify inputs satisfy bound constraints.

    • ModuleInput has the ability to store bounds checks the input values must satisfy.

  5. Leverage metadata to generate documentation.

    • Not strictly part of the field component, but the field component exposes enough information for another component to generate the documentation.