# Lesson 9.6: Using interface hierarchies

In the previous lesson, we improved the type hierarchies in our book data model by using the principle of composition over inheritance. In this lesson, we’ll explore the behaviour of interface hierarchies and see how they can be used to adapt our model in different ways.

Lesson 9.5 schema

```typeql
define
entity book @abstract,
  owns isbn, # abstract, but instantiated using isbn-10 or isbn-13
  owns isbn-13,
  owns isbn-10,
  owns title,
  owns page-count,
  owns genre,
  owns price,
  plays contribution:work,
  plays publishing:published;
entity paperback sub book,
  owns stock;
entity hardback sub book,
  owns stock;
entity ebook sub book;
entity contributor,
  owns name,
  plays contribution:contributor;
entity publisher,
  owns name,
  plays publishing:publisher;
entity place @abstract,
  owns name,
  plays locating:location,
  plays locating:located;
entity city sub place,
  plays publishing:location;
entity state sub place;
entity country sub place;
relation contribution,
  relates contributor,
  relates work;
relation authoring sub contribution;
relation editing sub contribution;
relation illustrating sub contribution;
relation publishing,
  relates publisher,
  relates published,
  relates location,
  owns year;
relation locating,
  relates located,
  relates location;
attribute isbn @abstract, value string;
attribute isbn-13, value string;
attribute isbn-10, value string;
attribute title, value string;
attribute page-count, value integer;
attribute genre, value string;
attribute price, value double;
attribute stock, value integer;
attribute name, value string;
attribute year, value integer;
```

## [](#_interfaces_as_types)Interfaces as types

In the PERA model, interfaces are themselves types, as we saw in [Lesson 9.1](../9.1-the-pera-model/index.md). Like the data storing types, interface types are able to form hierarchies. Role hierarchies are derived from their dependent relation type hierarchies, while ownership hierarchies are derived from their dependent attribute type hierarchies. Let’s examine an example.

```typeql
define
attribute isbn @abstract, value string;
attribute isbn-13 sub isbn;
attribute isbn-10 sub isbn;
```

As we saw in [Lesson 9.1](../9.1-the-pera-model/index.md), the `isbn` attribute type exposes an ownership interface, which we’ll call `isbn:OWNER` for this discussion. Likewise, `isbn-13` and `isbn-10` expose the interfaces `isbn-13:OWNER` and `isbn-10:OWNER` respectively. As `isbn-13` and `isbn-10` are subtypes of `isbn`, this means that `isbn-13:OWNER` and `isbn-10:OWNER` are subtypes of `isbn:OWNER`.

Role hierarchies work in a similar way, but have a major difference. Whereas all attribute types have an associated ownership interface, some relation types do not have role interfaces of their own, instead inheriting them from supertypes. We saw this in [Lesson 9.5](../9.5-composition-over-inheritance/index.md) with the following relation type hierarchy.

```typeql
define
relation contribution,
  relates contributor,
  relates work;
relation authoring sub contribution;
relation editing sub contribution;
relation illustrating sub contribution;
```

Here `contribution` defines two interfaces: `contribution:contributor` and `contribution:work`, and these are also inherited by its subtypes, which have no roles of their own. This means that all four relation types expose these same two interfaces. Consider the following instead.

```typeql
define
relation contribution,
  relates contributor,
  relates work;
relation authoring sub contribution,
  relates author as contributor;
relation editing sub contribution,
  relates editor as contributor;
relation illustrating sub contribution,
  relates illustrator as contributor;
```

Here `authoring`, `editing`, and `illustrating` each expose a new interface: `authoring:author`, `editing:editor`, and `illustrating:illustrator` respectively. These interfaces **override** the interface `contribution:contributor`, meaning that it is no longer usable by the subtypes of `contributor`. Because the newly defined interfaces override an interface from the parent type, these interfaces `authoring:author`, `editing:editor`, and `illustrating:illustrator` are subtypes of the parent interface `contribution:contributor`. Meanwhile, the interface `contribution:work` is still exposed by all four relation types.

In other words, overriding an inherited role is like a combination of subtyping and making the inherited value abstract:

```typeql
define
relation contribution,
  relates contributor,
  relates work;
relation authoring sub contribution,
  relates author as contributor,
  relates contributor @abstract;
# and also: authoring:author sub contribution:contributor
```

Since `contributor` is abstract in the `authoring` scope, it’s not available to be instantiated in the data. However, `authoring:author` is a subtype of `contribution:contributor` that can be used in its place.

## [](#_interface_variance)Interface variance

The way in which interface implementations are inherited is determined by their [variance](https://en.wikipedia.org/wiki/Covariance_and_contravariance_\(computer_science\)). Interface implementations, as defined in `owns` and `plays` statements, are:

*   **Covariant** in the implementing object type. This means that the statement _also_ applies to subtypes of the object type by inheritance.
    
*   **Invariant** in the implemented interface type. This means that the statement does _not_ also apply to subtypes of the interface type.
    

Let’s consider the following example, featuring two `plays` statements.

```typeql
define
entity book plays contribution:work;
entity contributor plays contribution:contributor;
```

In the first statement, the implementing object type is `book` and the implemented interface type is `contribution:work`. The object type has three subtypes `paperback`, `hardback`, and `ebook`, and they _also_ implement `contribution:work` by inheritance because the statement is covariant in `book`.

In the second statement, the implementing object type is `contributor` and the implemented interface type is `contribution:contributor`. The interface type has three subtypes `authoring:author`, `editing:editor`, and `illustrating:illustrator`, but `contributor` does _not_ also implement them because the statement is invariant in `contribution:contributor`.

Let’s consider another example, this time with an `owns` statement.

```typeql
define
entity book owns isbn;
```

The implementing object type is `book` and the implemented interface type is `isbn:OWNER`. This time, both have subtypes. The object type has three subtypes `paperback`, `hardback`, and `ebook`, and they _also_ implement `isbn:OWNER`. Meanwhile, the interface type has two subtypes `isbn-13:OWNER` and `isbn-10:OWNER`, and `book` does _not_ also implement them (nor do its subtypes).

Because implementations are invariant in the interface, this means that we must separately declare implementations of their subtypes if we wish to use them, as follows[1](#_footnotes).

```typeql
define
entity contributor plays authoring:author;
entity contributor plays editing:editor;
entity contributor plays illustrating:illustrator;
entity book owns isbn-13;
entity book owns isbn-10;
```

## [](#_using_role_overrides_effectively)Using role overrides effectively

Earlier in this lesson, we encountered two possible design strategies for the subtypes of `contribution`: one in which the subtypes inherit the role `contribution:contributor`, and one in which the role was overridden with specialized subtypes. The two strategies result in identical querying capabilities. When the role is inherited, we can query `contribution` and its subtypes with the following patterns:

*   To describe an instance of `contribution` or one of its subtypes:
    
    ```typeql
    contribution (contributor: $person, work: $book);
    ```
    
*   To describe an instance of a specific subtype of contribution (e.g. `authoring`):
    
    ```typeql
    authoring (contributor: $person, work: $book);
    ```
    
*   To describe an instance of `contribution` only, not one of its subtypes:
    
    ```typeql
    $rel isa! contribution (contributor: $person, work: $book);
    ```
    

When the role is overridden, we can do the same with the following patterns instead:

*   To describe an instance of `contribution` or one of its subtypes:
    
    ```typeql
    contribution (contributor: $person, work: $book);
    ```
    
    Here, using `contributor` in the relation tuple matches both `contribution:contributor` and its subtypes by inheritance polymorphism.
    
*   To describe an instance of a specific subtype of contribution (e.g. `authoring`):
    
    ```typeql
    authoring (author: $person, work: $book);
    ```
    
*   To describe an instance of `contribution` only, not one of its subtypes:
    
    ```typeql
    $rel isa! contribution (contributor: $person, work: $book);
    ```
    

The only thing that changes is the way we query the specific subtypes of the relation type. Even then, we can easily use role inference to omit the roles from the pattern as we saw in [Lesson 7.2](../../7-understanding-query-patterns/7.2-relation-patterns/index.md), and use the same queries throughout. Thus, the querying capabilities of the model are the same regardless of whether we inherit or override a role. In fact, this is true of any model involving roles that can be inherited or overridden.

However, there is a key difference in the way the roles must be implemented in these two cases. Consider the following definition:

```typeql
define
contributor plays contribution:contributor;
```

If `contribution:contributor` is inherited by the subtypes of `contribution`, then `contributor` will be able to play the role in those relation types as well, as they will all expose the same role interface. Conversely, if the role is overridden in the subtypes of `contribution`, then `contributor` will not be able to play roles in the subtypes as the `plays` statement is invariant in the role, as we saw above.

As a result, when building a relation type hierarchy, the primary factor to consider when deciding whether to inherit or override a role is whether role players of the relation supertype should also be role players of the relation subtypes. In this particular case, anyone capable of making a contribution to a book is also able to author, edit, or illustrate a book, by definition. As such, it makes most sense for the subtypes of `contribution` to inherit the role `contribution:contributor` rather than override it.

In other cases, it may be more appropriate to override roles. The following schema excerpt for a filesystem access management model is such an example. Here, we ensure that all users are able to execute system actions, but only admins are able to execute them with admin privileges.

```typeql
#!test[schema, reset]
define
entity user,
  plays action-execution:executor;
entity admin sub user,
  plays privileged-execution:privileged-executor;
relation action-execution,
  relates action,
  relates executor;
relation privileged-execution sub action-execution,
  relates privileged-executor as executor;
```

## [](#_footnotes)Footnotes

1.  [^](#_footnote_1) In some cases, it might seem cumbersome to have to explicitly define each implementation of an interface’s subtypes, but the invariance is essential to prevent logical fallacies in data model. Consider the following set of facts:
    
    *   All humans are animals.
        
    *   All geese are animals.
        
    *   All animals can join animal groups.
        
    *   All flocks are animal groups.
        
    *   All geese can join flocks.
        
    
    Now consider the following deduction:
    
    *   All animals can join animal groups, and all flocks are animal groups, therefore all animals can join flocks.
        
    
    If this deduction were correct, it would allow humans to join flocks, despite the fact that only geese should be able to join flocks! The deduction is obviously fallacious, specifically by [affirming the consequent](https://en.wikipedia.org/wiki/Affirming_the_consequent). Now consider if we translated our list of facts into TypeQL. It might look something like this:
    
    ```typeql
    #!test[schema]
    define
    entity animal @abstract;
    entity human sub animal;                      # all humans are animals
    entity goose sub animal;                      # all geese are animals
    entity animal-group @abstract;
    relation group-membership @abstract,
      relates group,
      relates group-member;
    entity animal-group plays group-membership:group;
    entity animal plays group-membership:group-member;  # all animals can join animal groups
    entity flock sub animal-group;                # all flocks are animal groups
    relation flock-membership sub group-membership,
      relates flock as group,
      relates flock-member as group-member;
    entity flock plays flock-membership:flock;
    entity goose plays flock-membership:flock-member;  # all geese can join flocks
    ```
    
    If definitions of interface implementations were covariant in the interface type instead of invariant, then `animal`, and all of its subtypes including `human`, would inherit the ability to play `flock-membership:flock-member`!
    

[Composition over inheritance](../9.5-composition-over-inheritance/index.md) [Avoiding interface redundancies](../9.7-avoiding-interface-redundancies/index.md)

[Edit on GitHub](https://github.com/typedb/typedb-docs/edit/3.x-development/academy/modules/ROOT/pages/9-modeling-schemas/9.6-using-interface-hierarchies.adoc) Edit this page on GitHub.