Skip to content

Commit 82d028f

Browse files
authored
add doc and examples for instantiates vs implements
1 parent 82ef9d9 commit 82d028f

4 files changed

Lines changed: 361 additions & 0 deletions

File tree

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
(howto-implements-instantiates)=
2+
# Implements vs Instantiates vs Inheritance: A Best Practices Guide
3+
4+
LinkML offers several mechanisms for relating schema elements to one another:
5+
**`is_a` + `mixins`** (inheritance), **`implements`** (structural conformance),
6+
and **`instantiates`** (metamodel extension). This guide clarifies when and why
7+
to use each one.
8+
9+
## Quick Reference
10+
11+
| Mechanism | Relationship | Analogy | What it governs |
12+
|-----------|-------------|---------|-----------------|
13+
| `is_a` | "is a kind of" | Class inheritance (OOP) | Slot inheritance, type hierarchy |
14+
| `mixins` | "also behaves like" | Traits / interfaces (OOP) | Slot inheritance without single-parent constraint |
15+
| `implements` | "conforms to the structure of" | Interface implementation | Declares structural conformance to a template |
16+
| `instantiates` | "is an instance of (at the meta level)" | Metaclass instantiation | Governs which annotations are valid on a schema element |
17+
18+
## `is_a` and `mixins` — Classical Inheritance
19+
20+
Use `is_a` and `mixins` when your classes form a genuine **type hierarchy**
21+
where a child class truly *is a kind of* the parent.
22+
23+
```yaml
24+
classes:
25+
NamedThing:
26+
abstract: true
27+
slots:
28+
- id
29+
- name
30+
31+
WithProvenance:
32+
mixin: true
33+
slots:
34+
- created_by
35+
- created_on
36+
37+
Person:
38+
is_a: NamedThing
39+
mixins:
40+
- WithProvenance
41+
slots:
42+
- birth_date
43+
```
44+
45+
Key properties:
46+
47+
- **`is_a` forms a tree** — each class has at most one `is_a` parent.
48+
- **`mixins` form a DAG** — a class can mix in multiple mixin classes, avoiding
49+
the [diamond problem](https://en.wikipedia.org/wiki/Mixin).
50+
- **Slots are inherited.** `Person` above inherits `id`, `name`, `created_by`,
51+
and `created_on` without redeclaring them.
52+
- **`slot_usage` refines inherited slots** in the context of the child class
53+
(e.g. making an inherited slot required).
54+
55+
**When to use:** You are modeling a domain ontology where child classes are
56+
genuinely subtypes of parent classes. Data validated against the child class
57+
should also be valid against the parent class (Liskov substitution).
58+
59+
## `implements` — Structural Conformance
60+
61+
Use `implements` when a class **conforms to a structural template** without
62+
being a subtype of it. Think of it like implementing an interface or satisfying
63+
a contract.
64+
65+
```yaml
66+
classes:
67+
FieldObservation:
68+
description: >-
69+
Contract for field observation classes.
70+
Any class that implements this must provide these slots.
71+
slots:
72+
- location
73+
- timestamp
74+
- observer
75+
76+
RadonObservation:
77+
implements:
78+
- geochem_profile:FieldObservation
79+
slots:
80+
- location
81+
- timestamp
82+
- observer
83+
- radon_activity
84+
```
85+
86+
Key properties:
87+
88+
- **No slot inheritance.** Unlike `is_a`, `implements` does not automatically
89+
pull slots into the implementing class. You must declare them yourself.
90+
- **Cross-schema references.** The value of `implements` is a `uriorcurie`,
91+
so you can reference classes from external schemas without importing them.
92+
- **Documentative intent.** `implements` declares that your class is *intended*
93+
to conform to a contract. Tooling support for enforcement is evolving.
94+
95+
**When to use:** You want to declare that your class or slot satisfies a
96+
structural contract defined elsewhere, but it is *not* a subtype of that
97+
contract class. This is especially useful when:
98+
99+
- The contract comes from an external schema you do not want to import wholesale.
100+
- Your class belongs to a different type hierarchy but must meet certain
101+
structural requirements.
102+
- You are working with schema profiles (see [Schema Profiles](#schema-profiles)
103+
below).
104+
- A slot implements a well-known semantic property (see below).
105+
106+
### `implements` for Semantic Properties
107+
108+
`implements` can also be used at the **slot level** to declare that a slot
109+
represents a well-known semantic property. For example, to declare that a
110+
`name` attribute implements `rdfs:label`:
111+
112+
```yaml
113+
classes:
114+
OntologyTerm:
115+
attributes:
116+
id:
117+
identifier: true
118+
name:
119+
implements:
120+
- rdfs:label
121+
```
122+
123+
This is the recommended approach for binding validation in the
124+
[linkml-term-validator](https://linkml.io/linkml-term-validator/binding-validation/#1-using-implements-recommended),
125+
where it signals which field holds the canonical label for ontology term
126+
lookups.
127+
128+
## `instantiates` — Metamodel Extension
129+
130+
Use `instantiates` when a schema element **is an instance of a metaclass** that
131+
governs what *annotations* (metadata about the schema element itself) are valid.
132+
This operates at the *meta* level — it does not affect the data instances, but
133+
rather the schema element's own metadata.
134+
135+
```yaml
136+
classes:
137+
RadonObservation:
138+
instantiates:
139+
- geochem_profile:GeochemClass
140+
annotations:
141+
provenance_status: "validated" # governed by GeochemClass
142+
data_steward: "smoxon" # governed by GeochemClass
143+
slots:
144+
- radon_activity
145+
```
146+
147+
Where `GeochemClass` is defined as:
148+
149+
```yaml
150+
# In geochem_profile schema
151+
classes:
152+
GeochemClass:
153+
description: "Any class in a geochem schema must have provenance"
154+
class_uri: geochem_profile:GeochemClass
155+
attributes:
156+
provenance_status:
157+
range: string
158+
required: true
159+
data_steward:
160+
range: string
161+
```
162+
163+
Key properties:
164+
165+
- **Governs annotations, not data slots.** `instantiates` controls which
166+
annotation tags are valid on the schema element.
167+
- **Metaclass relationship.** The instantiated class acts as a metaclass — its
168+
slots define the valid annotation keys for the instantiating element.
169+
- **Works on any schema element.** Classes, slots, enums, and even schemas
170+
themselves can use `instantiates`.
171+
- **Validation is declarative.** As of LinkML 1.6+, `instantiates` declares
172+
intent. Full validation enforcement is under active development.
173+
174+
**When to use:** You want to constrain or extend the *metadata* that schema
175+
authors must provide on their schema elements (not the data those elements
176+
describe).
177+
178+
## Putting It All Together
179+
180+
Here is a complete example showing all three mechanisms working in concert,
181+
adapted from a geochemistry schema profile:
182+
183+
```yaml
184+
# geochem_profile.yaml — defines rules for geochem schemas
185+
186+
id: https://example.org/geochem-profile/
187+
name: geochem_profile
188+
prefixes:
189+
geochem_profile: https://example.org/geochem-profile/
190+
linkml: https://w3id.org/linkml/
191+
192+
classes:
193+
# Metaclass: governs annotations on classes
194+
GeochemClass:
195+
description: "Any class in a geochem schema must have provenance metadata"
196+
class_uri: geochem_profile:GeochemClass
197+
attributes:
198+
provenance_status:
199+
range: string
200+
required: true
201+
data_steward:
202+
range: string
203+
204+
# Metaclass: governs annotations on slots
205+
GeochemSlot:
206+
description: "Any slot in a geochem schema must declare units"
207+
class_uri: geochem_profile:GeochemSlot
208+
attributes:
209+
unit_ontology_term:
210+
range: uriorcurie
211+
212+
# Structural contract: defines required slots for field observations
213+
FieldObservation:
214+
description: "Contract for field observation classes"
215+
slots:
216+
- location
217+
- timestamp
218+
- observer
219+
```
220+
221+
Then in a downstream schema:
222+
223+
```yaml
224+
# radon_schema.yaml — a domain schema that uses the profile
225+
226+
id: https://example.org/radon/
227+
name: radon_schema
228+
prefixes:
229+
geochem_profile: https://example.org/geochem-profile/
230+
231+
classes:
232+
RadonObservation:
233+
# META-LEVEL: "I am an instance of your metaclass"
234+
# → governs which annotations are valid on this class
235+
instantiates:
236+
- geochem_profile:GeochemClass
237+
238+
# STRUCTURAL: "I conform to your structural contract"
239+
# → declares that this class provides the slots required by FieldObservation
240+
implements:
241+
- geochem_profile:FieldObservation
242+
243+
# Annotations governed by instantiates (GeochemClass attributes)
244+
annotations:
245+
provenance_status: "validated"
246+
data_steward: "smoxon"
247+
248+
# Data slots — some from implements contract, some domain-specific
249+
slots:
250+
- location # ← required by FieldObservation contract
251+
- timestamp # ← required by FieldObservation contract
252+
- observer # ← required by FieldObservation contract
253+
- radon_activity # ← domain-specific
254+
255+
slots:
256+
radon_activity:
257+
# META-LEVEL: "I am an instance of your slot metaclass"
258+
instantiates:
259+
- geochem_profile:GeochemSlot
260+
annotations:
261+
unit_ontology_term: "UO:0000134" # governed by GeochemSlot
262+
range: float
263+
264+
location:
265+
range: string
266+
timestamp:
267+
range: datetime
268+
observer:
269+
range: string
270+
```
271+
272+
Notice how:
273+
274+
- **`instantiates`** operates at the meta level, governing annotations
275+
(`provenance_status`, `data_steward`, `unit_ontology_term`).
276+
- **`implements`** operates at the structural level, declaring conformance to
277+
a slot contract (`location`, `timestamp`, `observer`).
278+
- Neither `instantiates` nor `implements` *inherits* slots — that is the job
279+
of `is_a` and `mixins`.
280+
281+
## Decision Flowchart
282+
283+
When deciding which mechanism to use, ask yourself:
284+
285+
1. **Is the child a genuine subtype of the parent?**
286+
→ Use `is_a` (and `mixins` for cross-cutting concerns).
287+
288+
2. **Does the class need to satisfy a structural contract from another schema?**
289+
→ Use `implements`.
290+
291+
3. **Do I need to govern what metadata (annotations) schema authors can attach
292+
to a schema element?**
293+
→ Use `instantiates`.
294+
295+
4. **Do I need more than one?**
296+
→ Yes! These mechanisms are complementary. A single class can use `is_a`,
297+
`mixins`, `implements`, and `instantiates` simultaneously.
298+
299+
(schema-profiles)=
300+
## Schema Profiles
301+
302+
The `implements` and `instantiates` mechanisms are foundational to the concept
303+
of **schema profiles** — reusable sets of constraints and conventions that
304+
downstream schemas can adopt.
305+
306+
The [linkml-microschema-profile](https://github.com/linkml/linkml-microschema-profile)
307+
project provides an example of this pattern. It defines a small, generic profile
308+
that schema authors can reference to ensure their schemas follow common
309+
conventions (e.g. requiring descriptions on all classes, enforcing naming
310+
patterns).
311+
312+
Profiles are useful when:
313+
314+
- An organization wants to enforce governance rules across many schemas without
315+
requiring full schema imports.
316+
- Domain-specific conventions (like the geochemistry example above) need to be
317+
separated from generic best practices.
318+
- You want to layer constraints: a generic profile for all schemas, plus a
319+
domain profile for your specific field.
320+
321+
For instance, generic constraints like "all classes must have descriptions"
322+
belong in a base profile, while domain-specific metaclasses like
323+
`GeochemClass` belong in a domain-specific child profile. See
324+
[issue #3282](https://github.com/linkml/linkml/issues/3282) for discussion on
325+
separating generic from domain-specific profile components.
326+
327+
## Common Pitfalls
328+
329+
**Pitfall 1: Using `is_a` when you mean `implements`.**
330+
If your class is not genuinely a subtype — it just happens to share some
331+
slots — prefer `implements`. Misusing `is_a` creates misleading type
332+
hierarchies.
333+
334+
**Pitfall 2: Expecting slot inheritance from `implements`.**
335+
Unlike `is_a`, `implements` does not pull in slots. You must declare the
336+
required slots explicitly in your class.
337+
338+
**Pitfall 3: Confusing `instantiates` with `implements`.**
339+
`instantiates` governs *annotations on the schema element itself* (metadata
340+
about the class/slot). `implements` governs *the structure of data instances*
341+
(which slots the class provides). They operate at different levels.
342+
343+
**Pitfall 4: Using `instantiates` to try to add data slots.**
344+
`instantiates` controls annotation validity, not data structure. If you need
345+
to add data slots, use `is_a`, `mixins`, or declare them directly.
346+
347+
## Summary
348+
349+
| Question | Use |
350+
|----------|-----|
351+
| "Is RadonObservation a kind of Observation?" | `is_a: Observation` |
352+
| "Should RadonObservation also behave like a Locatable thing?" | `mixins: [Locatable]` |
353+
| "Does RadonObservation conform to the FieldObservation contract?" | `implements: [geochem_profile:FieldObservation]` |
354+
| "What metadata must the RadonObservation *class definition* carry?" | `instantiates: [geochem_profile:GeochemClass]` |

docs/howtos/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ Some of these guides are works in progress
2121
generate-ai-prompts
2222
deprecating-elements
2323
ontologies-as-values
24+
implements-instantiates-guide
2425
skip-logic

docs/schemas/annotations.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,6 @@ classes:
157157
- mymetamodel:StrictElement
158158
description: A person, living or dead ## must be provided
159159
```
160+
161+
See also the {ref}`howto-implements-instantiates` guide for a comparison of
162+
`instantiates`, `implements`, and classical inheritance (`is_a` / `mixins`).

docs/schemas/inheritance.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ Note that `is_a` has the characteristics of homeomorphicity: `is_a` **SHOULD** o
110110

111111
See also the [Wikipedia page on mixins](https://en.wikipedia.org/wiki/Mixin)
112112

113+
For a comparison of `is_a` / `mixins` with `implements` and `instantiates`, see
114+
the {ref}`howto-implements-instantiates` guide.
115+
113116
## Materializing inherited slots
114117

115118
the [linkml generator](../generators/linkml) can be used with the

0 commit comments

Comments
 (0)