Subtyping

Overview

Defs are organized into a tree structured taxonomy via the is tag. A def is a subtype if it defines a subset or narrowing of a broader term. For example we say that water is a subtype of liquid because it is a specific type of liquid. The converse is that liquid is a supertype of water.

In the context of natural language, a subtype is a hyponym and a supertype is a hypernym. If you have ever played Mad Libs, then subtyping is intuitively like filling in the blanks. For example if we say that a pipe conveys a fluid, then the term fluid encompasses specific types of fluids including water, steam, gasoline, or fuel oil.

We can also evaluate subtyping in the context of set theory. If we say that elec-meter is a subtype of meter, then we are saying the set of all things that are elec meters is wholly contained by the set of all meters.

Subtyping is a powerful knowledge modeling tool. However remember that modeling the real world is a messy business. Its impossible to design a taxonomy that fits all situations. Consider the platypus and its difficulty to fit into a biological taxonomy. Haystack attempts to define a pragmatic approach between simplicity and the common use cases. But you will likely find use cases which aren't a perfect fit for Haystack's ontology.

Transitivity

Subtyping is defined to be a transitive relationship. If B is a subtype of A and C is a subtype of B, then it is implied that C is a subtype of A. A concrete example: water is a subtype of liquid and liquid is a subtype of fluid, therefore water is inferred to be a subtype of fluid.

Is Tag

Subtyping is declared via the is tag on a def. The value of the is tag is a symbol or list of symbols. Since most defs subtype from a single def, we typically use just a symbol:

def: ^water
is: ^liquid

In cases where multiple supertypes are required, use a list of symbols:

def: ^weather
is: [^air, ^solar, ^wind]

All terms are required to have an is tag with the exception of the root terms which are marker, val, and feature. Feature keys and compose defs have implied subtyping rules and must not declare an is tag.

It is invalid to create cyclic subtyping relationships. Subtyping must result in a strict tree structure.

Inheritance

Defs are themselves specified a set of tag name/value pairs. Subtypes automatically inherit these tags into their own definitions. We call this process inheritance and it works similar to inheritance in traditional object-oriented languages.

We call the tags explicitly defined in a def the declared tags. The tags that are effective after inheritance is applied are called the normalized tags. Normalization rules are as follows:

  1. Start with the declared types from the def
  2. Each supertype is processed in order of declaration in the is tag
  3. The supertype must be fully normalized itself
  4. Filter out any tags from the supertype marked as notInherited
  5. Inherit all tags from step 3 which are not yet declared or inherited from other supertypes

In the case of ambiguity from multiple inheritance, a subtype should explicitly declare the tag value.

Consider this completely fictional example for an El Camino which is a hybrid between a car and a pickup truck:

// declarations
def: ^car
numDoors: 4
color: "red"
engine: "V8"
----
def: ^pickupTruck
numDoors: 2
color: "blue"
bedLength: 80in
----
def: ^elCamino
is: [^pickup, ^car]
color: "purple"

The normalized definition with inheritance would be:

def: ^elCamino        // declared
is: [^pickup, ^car]   // declared
color: "purple"       // declared
numDoors: 2           // inherited from pickup first
engine: "V8"          // inherited from car
bedLength: 80in       // inherited from pickup