Defs define our meta-model and dicts define our data instances. Haystack provides a standardized processes to map between the two:
We say a dict implements a def when the it contains the appropriate tags. For example if a dict defines the equip tag, then the dict implements the equip def. If a dict contains both the hot and water tags then the dict implements hot-water.
Reflection is the inverse process. We are given a dict and we compute the list of effective defs which the it implements.
Implementation and reflection in Haystack are a flavor of structural typing. Dict data never explicitly declares itself of a single class or type. Instead we use a combination of marker tags to indicate typing information. We use value tags to indicate an attribute or fact about the data.
The rules for a dict to implement a def are as follows:
Lets look at some examples.
If we want to apply the point def to a dict, then we apply rule 1a. There are no supertypes marked as mandatory
so we are done. Thus we apply only {point}
to fully implement the point def.
If we wish to apply the rtu def, then we apply rule 1a. However when we walk the supertype tree we see that it subtypes from two mandatory
defs: ahu and equip. Therefore to implement rtu requires adding all three tags: {rtu, ahu, equip}
. As a general rule top-level entity markers such as site, space, equip, point are always required. This makes it easy to query your tag based data.
If we wish to apply the hot-water-plant def then we will apply rule 1b. Additionally this def subtypes from the mandatory equip tag. So we need to add four tags {hot, water, plant, equip}
.
When a dict implements a def, it implicitly implements all of that def's supertypes. For example if we add the water tag to a dict, then we implicitly implement all of the supertypes of water which includes liquid, fluid, substance, phenomenon, and marker. We call this flattened list of supertypes the def's inheritance.
Reflection is the inverse of implementation. Given a dict we compute the defs it implements. Reflection is always done within the context of a namespace to determine which defs are in scope.
To reflect a dict to its def, we walk each tag in the dict and apply the following rules:
Lets follow this process to reflect the following dict:
id: @hwp, dis: "Hot Water Plant", hot, water, plant, equip
Step 1 yield: id, dis, hot, water, plant, equip
Step 2 yields: hot-water, hot-water-plant
Step 3 yields:
We use markers extensively in Haystack to indicate typing information. However a marker tag is not a type per se. In a tagging system we use tags to quickly and easily associate data to related terms. Lets examine our example from above:
id: @hwp, dis: "Hot Water Plant", hot, water, plant, equip
This dict implements the term hot-water, but we would not say that the plant "is-a" a type of hot-water. Those tags only mean that this data is related to hot-water. This makes it convenient to query our data for things related to hot-water, water, or liquid.
However we would say that the dict above "is-a" type of hot-water-plant. We can reflect this information because hot-water-plant is a subtype of entity. The entity
subtype tree models primary typing information. Terms which subtype from entity must be designed to stand on their own - they cannot be used in conjuncts.