Simple-to-Generic type conformance

Hi everyone, I came across type conformance in BMM. In simple type, rules for conformance of simple to simple are specified, but I could’t find the description for simple-to-generic conformance.

for example Multiplicity_interval inherits Interval<Integer> and so Multiplicity_interval conforms Interval<Ordered>. in other case in if Simple inherits simple as theoretical GrandChild inherits Multiplicity_interval but GrandChild should also conform to Interval<Ordered>.

Is this case already specified or my logic is wrong?

a suggestion that might help if it is planned to be added, I implemented it according to this rule:
a simple type A conforms to a generic type Gb if:

  1. A directly inherits from some type P (where P can be Simple or Generic); AND

  2. P conforms to Gb.

and this can recursively call typeConformsTo as the following:
typeConformsTo(GrandChild, Interval<Ordered>GrandChild is Simple. It looks at its parent: Multiplicity_interval so recursetypeConformsTo(Multiplicity_interval, Interval<Ordered>)Multiplicity_interval is Simple. It looks at its parent: Interval<Integer>, so RecursetypeConformsTo(Interval<Integer>, Interval<Ordered>)True

Your reasoning is correct. I never specified it specifically, because I assumed that real engineers would know the standard inheritance rules from generic types to descendant types, i.e. pretty much as found in Java, Kotlin, and I think even C++.

It is very logical reasoning, but the languages mentioned handle this very differently: C++ templates are invariant by default (Interval<Integer> has no relationship to Interval<Ordered>, it wouldn’t even compile), in Java/Kotlin generics are invariants and require explicit variance markers or wildcards to achieve this.

Since this behavior is fundamentally an Eiffel-style covariance concept, leaving it to the implementer’s ‘intuition’ will inevitably lead to fragmented implementations based on their specific language background. To ensure BMM is clear, deterministic and language-agnostic, shouldn’t this concrete rule be formally part of the spec?

I actually couldn’t remember the C++ rule, but I don’t know of anyone using C++ in openEHR or even much in healthcare (cf telecoms or finance). Eiffel allows covariance of types; in Kotlin you can use ‘extends’ and so on to get right effect.

My general approach has been to make sure that whatever is in the models can be expressed in Java (let’s say minimum Java 11) and Kotlin. We don’t have much generics in openEHR, to avoid problems. For languages with no generics (e.g. XML schema), effective equivalent types have to be created, e.g. Interval → Interval_integer and so on.

We should document this better, I agree.

You are right it is rarely used in healthcare, I am trying different approach than classical implementations, I am building an Interpreter in C++ implementing BMM and relying entirely on schemas loaded to memory + implementing functions in the native language, this supports serialization and deserializaton, running expression, validation all in one place with cross RM versions stability, which I think C++ would provide better performance and more effecient memory use than JVM languages.

thank you for your time.

Just for fun, this is what BMM4 looks like.

+-------------------------------------------------------------------------------------
|    description:: Boolean type
|    documentation:: https://splash.org
|    keywords::    SPLASH representation model
|    author::      Thomas Beale <thomas.beale@splash.org>
|    copyright::   Copyright (c) 2025- SPLASH <https://www.splash.org>
|    license::     Apache 2.0 <http://www.apache.org/licenses/LICENSE-2.0.html>
+-------------------------------------------------------------------------------------

|
| Built-in class representing minimal interface of Boolean type.
|
builtin class Boolean
    is_a Any

feature_group("operators")

    |
    | Logical conjunction (and-ing) of `Self` with `other`.
    |
    func conjunction (other: Boolean): Boolean
        alias 
            "and", "∧" ;
        post_cond
            post_de_Morgan: Result = ¬(¬Self ∨ ¬other);
            post_commutative: Result = (other ∧ Self);
        ;

    |
    | Logical disjunction (or-ing) of `Self` with `other`.
    |
    func disjunction (other: Boolean): Boolean
        alias 
            "or", "∨" ;
        post_cond
            post_de_Morgan: Result = ¬(¬Self ∧ ¬other);
            post_commutative: Result = (other ∨ Self);
            post_consistent_with_semi_strict: Result ⇒ (Self ∨ other);
        ;

    |
    | Logical exclusive or of `Self` with `other`.
    |
    func exclusive_disjunction (other: Boolean): Boolean
        alias 
            "xor", "⊻" ;
        post_cond
            post_definition: Result = (Self ∨ other) ∧ ¬(Self ∧ other);
        ;

    |
    | Boolean implication of `other` (semi-strict).
    |
    func implication (other: Boolean): Boolean
        alias 
            "implies", "⇒" ;
        post_cond
            post_definition: Result = (¬Self ∨ other);
        ;

    |
    | Boolean negation of the current value.
    |
    func negation: Boolean
        alias 
            "not", "¬" ;
        ;

invariant
    involutive_negation: is_equal (¬(¬Self)) ;
    non_contradiction: ¬(Self ∧ (¬Self)) ;
    completeness: Self ∨ (¬Self) ;

end

It obviously would, but it doesn’t matter for modelling use cases, e.g. single users with a tool like IntelliJ. It would certainly make a difference when the same libraries are deployed at scale in a backend serving thousands of online application sessions…

@thomas.beale You are definitely right. It wouldn’t matter in modelling, but rather on backend servers; in my case, I want to compile both ADL and BMM and, based on them, run validations and expressions (which would require manually managing memory), etc., regardless of minor changes in versions of the RM or AM.

​So, C++ would be a better choice for building interpreters and runtimes with predictable memory management. But Java is a much better choice if openEHR is implemented like Archie (for faster development and safety); using C++ in such a way is more prone to memory leaks and pointer errors. It is all about weighing advantages and disadvantages.

​This strategy essentially treats openEHR as a “Domain-specific language” (DSL) environment rather than just a static specification for medical records. To dive deeper into these technicalities, I’d like to inquire further about the primitive types. I’ve noticed that BMM borrows heavily from Eiffel’s design philosophy while keeping an eye on Java compatibility, but sometimes they aren’t totally compatible. For example, Eiffel Strings are a mutable reference type, while expanded types (Integer, Real, Boolean, Character) are immutable (and non-reference). Java, on the other hand, handles strings as immutable references.

​This distinction is not merely an implementation detail; it fundamentally changes the outcome of expressions. If a runtime treats a String as a mutable reference, a ‘side-effect’ in a validation rule could mutate the underlying RM instance.

​Example:

original_text = o.events[at0003].data.items[at0001].value.value –- “hypertension”

formatted_text = original_text

-- We perform an operation with a side effect
formatted_text.to_upper()

-- Assertion
assert (original_text == "hypertension")

​Note: this is hypothetical pseudocode. I know that, for now, String functions just return a new String instead of mutating, but this is just to demonstrate how technical info can affect the result.

​Given this, I would like to clarify the intended design of the openEHR primitives:

  1. ​Are the primitives immutable?

  2. ​Are they assigned by value or reference?

  3. ​String, specifically: is it a reference, is it immutable, and is it assigned by value?

It’s a good point: these details should be updated in the specs to be clearer.

I don’t think anything in the current specs implies that Strings are mutable, i.e. invariant expressions or whatever. But you are right, the definition of primitive types doesn’t indicate that Strings are immutable. FWIW, I did originally just use Eiffel semantics, i.e. mutable strings, but even Eiffel changed over the years to the more common immutable form of String. Anyway, I don’t think anything other than something in the data types introduction would need to be added to indicate that Strings are immutable.

There might be assignment statements (‘:=’) somewhere in the specs, I don’t remember. If so, assignment of a String is always by reference. To make a copy is some sort of copy() or clone() call in most languages.

If I received that question, I’d answer with “representing values of types which are frequently referred to as primitive types in most mainstream programming languages” That’s because we keep banging on about openEHR being a technology agnostic language and if the specifications included statements about implementation of concepts, such as how the mutability of primitive types are assumed to be implemented, they would not be technology agnostic then.

The same reasoning would cause me to say “variance behaviour of generics is undefined/left to implementations” because different programming languages support different behaviour, which may even change for certain types in time. Assuming any variance choice (co/contra) comes with various language specific side effects: java’s use site, C#’s declaration site.

I would say declaring some aspects of the specs as out-of-scope is OK, and both aspects you raised are too implementation specific to pass the threshold for me.

I am not a real engineer though, so I’ll let real engineers decide.

Hi Seref,

I appreciate that perspective, and I totally agree that openEHR should remain technology-agnostic. However, I believe there is a vital distinction between implementation details (how a machine manages logic and runs memory ) and semantic behavior (how a language guarantees results).

When we move from the Reference Model (RM) into the territory of the “Basic Meta-Model (BMM)” and “Expression Languages”, we are effectively designing a Domain-Specific Language (DSL). For a DSL to be truly agnostic, the result of an expression must be identical regardless of the underlying technology it is implemented in.

Diving into these “technicalities” isn’t an abandonment of agnosticism; rather, it is the process of rectifying ambiguity. Consider these industry specs:

  • Java itself: The specification is technology-agnostic (implemented by HotSpot, OpenJ9, etc.), yet it strictly defines primitive behavior and memory semantics to ensure “Write Once, Run Anywhere.”

  • ECMAScript (JavaScript): Whether implemented in C++ (Node.js/V8), Zig (Bun), or C++ (WebKit), the specification dictates exactly how strings and objects behave. Without this “technical” specification, the web would break across different browsers.

In all these languages and specs it defines a solid foundation that all implementers should abide by, like typing(which BTW, is greatly defined in BMM), core syntax primtives, and objects, etc. and none specify how underlying machinery work like their garbage collector for example. I know OpenEHR puropse isn’t like general purpose languages but sematic consistency still essential.

Similarly, if the BMM/EL remains silent on mutability or variance, we risk a “fragmented truth” where a clinical validation passes on a Java-based server but fails—or worse, causes a side effect—on a C++ or C# implementation.

In my view, specifying that a “String is immutable” or defining “variance rules” isn’t over-stepping into implementation; it is defining the contract that implementers must fulfill. To ensure uniform clinical safety, the “what” (the result) must be standardized, even if the “how” (the code) remains entirely up to the developer.

But, also not an engineer, and I may be looking at this with POV of a developer (and an M.D. who values predictable outcomes!), but I believe that for openEHR BMM and Expression Language to function as a reliable computational environment, these semantic foundations need to be solid.

and thank you for your efforts.

If we are talking BMM/EL, and not just openEHR RM, then it does need to be fully specified. The version of BMM I have most recently implemented is essentially a programming language. I have yet to build a new version of the spec, which will include these sorts of things.

Until then, I would suggest that you make assumptions that are the most natural for today’s mainstream programming languages , including string immutability and so on.

Yep, that is exactly the conclusion I reached after seeing your earlier examples. It reinforces the idea that BMM/EL is evolving into a language heavily that’s dependent on reflection and metadata—one where the models can be imported, compiled, and executed as a cohesive logic layer for validation, serialization and deserializtion etc.

Thank you for the support and for the excellent work you’ve done on the specifications; it provides the necessary architectural rigor to make these high-performance implementations possible.