Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Composition of interface implementations as high-level abstractions (traits) #327

Open
james-whiteside opened this issue Apr 9, 2024 · 0 comments

Comments

@james-whiteside
Copy link

Overview

There are often times when it would be useful to be able to collect interface implementations (attributes owned and roles played) into high-level abstractions. In many data models, this can result in very large reductions in the complexity of schema definitions and queries, and increased clarity in the same. This also closes the gap between the capabilities of TypeDB and OOP, further reducing object model mismatch.

Current languages capabilities

Consider the following schema excerpt.

define
person sub entity,
    abstract,
    owns name;
racing-driver sub person,
    plays car-ownership:owner,
    owns licence-number,
    owns penalty-points,
    owns competitive-rank,
    owns nationality-represented;
courier sub person,
    plays car-ownership:owner,
    owns licence-number,
    owns penalty-points;
dentist sub person,
    owns licence-number;
car-dealer sub person,
    plays car-ownership:owner;
chess-player sub person,
    owns licence-number,
    owns penalty-points,
    owns competitive-rank,
    owns nationality-represented;

In this schema, if we want to query for people who can drive, we must use one the following or similar:

match
$p isa $career;
{
    $career type racing-driver;
} or {
    $career type courier;
};
not { $p has penalty-points >= 12; };
fetch
$p: name;
match
$p isa $career;
$career plays car-ownership:owner;
$career owns licence-number;
$career owns penalty-points;
not { $p has penalty-points >= 12; };
fetch
$p: name;

Neither is a good solution. The first is vulnerable to false negatives when new careers are introduced that allow driving, requiring those new careers be added to the query. The second is vulnerable to false positives if a career involves the same interfaces implemented, which would (in the worst case) require ownership of a dummy attribute type-is-driver to indicate that the career is one that allows driving. Grouping racing-driver and courier under a driver supertype is not a good move, as we might want to do other groupings like racing-driver and chess-player under a competitor supertype. This is a classic use case for composition over inheritance, but the composition capabilities of TypeQL are currently limited.

Proposed feature

These problems could be solved by allowing the composition of interface implementations into high-level abstractions, called "traits" akin to those in many OOP languages. A new root type trait is proposed. Its subtypes would always be abstract and able to implement interfaces as object types can. A trait could then be implemented by an object type using a new implements keyword. This would cause the object type to implement all interface implementations defined on the trait.

In the following example, we define and then implement driver and competitor as traits.

define
person sub entity,
    abstract,
    owns name;
driver sub trait,
    plays car-ownership:owner,
    owns licence-number,
    owns penalty-points;
competitor sub trait,
    owns competitive-rank,
    owns nationality-represented;
racing-driver sub person,
    implements driver,
    implements competitor;
courier sub person,
    implements driver;
dentist sub person,
    owns licence-number;
car-dealer sub person,
    plays car-ownership:owner;
chess-player sub person,
    implements competitor,
    owns licence-number,
    owns penalty-points;

Now, in order to query for people who can drive, we can use the following.

match
$p isa $career;
$career implements driver;
not { $p has penalty-points >= 12; };
fetch
$p: name;

Or even the following, by casting of object types into trait types.

match
$p isa driver;
not { $p has penalty-points >= 12; };
fetch
$p: name;

With very large schemas, it can be imagined how this feature would reduce verbosity of schemas and queries significantly, as well as increasing the expressivity of the language.

Additional benefits in TypeDB 3.0

With the introduction of more powerful annotations, traits would be even more useful.

define
driver sub trait,
    plays car-ownership:owner,
    owns licence-number @card(1),
    owns penalty-points @default(0);
match
$p isa driver, has penalty-points < 12;
fetch
$p: name;

Even more so after the introduction of query templates.

define
fun can-drive(person: $p) -> bool:
    $p isa driver, has penalty-points < 12;
    return; check;
match
can-drive($p);
fetch
$p: name;

Challenges remaining

There remain challenges. Obviously, the syntax suggested is only a first pass, but there would be additional complexity introduced to the language regardless of the final syntax. This would lead to syntax bloat, but the gain here would likely outweigh the cost.

More difficult, some ambiguous situations can be envisioned. For instance, how would an object type that implemented two traits be treated, if those two traits implement the same interface with conflicting annotations (e.g. cardinality).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants