Architectural Discussion: Inheritance Patterns, Generic Containers, and Override Semantics in BMM

Hi everyone,

I am currently working on the implementation of a BMM-based runtime. As I map out the memory model and semantic analysis logic, I have identified a few patterns in the BMM specification that I’d love to get your thoughts on. I want to ensure my implementation aligns with the intended architectural vision of openEHR.

1. Representation of Generic Containers

in BMM spec it states that inheritance is from class to types, and it is very logical. The types of container classes are represented using BMM_CONTAINER_TYPE: BMM_TYPE, on the other hand BMM_CLASS::get_ancestors(): Hash<string, BmmModelType>, so ancestors are represented using BMM_MODEL_TYPE, which inherits BMM_UNITARY_TYPE rather that BMM_CONTAINER_TYPE which inherit BMM_TYPE directly.

based on that what do you think is the best way to represent for example class List<T>: Container<T>, one way in my mind is to make an exception for declartion cases like this one and represent the ancestor Container<T> as below do think this is an acceptable approach?

BMM_GENENRIC_TYPE {
   base_class: BMM_SIMPLE_CLASS("Container"), 
   generic_parameters: [ BMM_PARAMETER_TYPE("T") ]
} 

This representation came to my mind after seeing the the code below in a previous .bmm schema(I didn’t check the latest) regarding RESOURCE_DESCRIPTION_ITEM.original_resource_uri, which allowed List to have Hash as item_type, even though item type should be unitary in BMM_CONTAINER_TYPE

type_def = <
container_type = <“List”>
type_def = (P_BMM_GENERIC_TYPE) <
root_type = <“Hash”>
generic_parameters = <“String”, “String”>
>

  • Question: Is this the intended design? Can container classes be represented interchangeably as a BMM_CONTAINER_TYPE or a BMM_UNITARY_TYPE (Generic) depending on the context of the usage, and What do you think is the best way to handle the generic class declarations, i.e List<T>: Container<T> in BMM in-memory model.

2. Generic Ancestor Descriptions

In reviewing .bmm schemas, I noticed variations in how generic ancestors are described, some use just ancestors where more complex ones use ancestor_defs, for in some classes like Proper_interval<T>: Interval<T>, it is understandable to drop ancestors since both have generic parameter T:

  • Hash<K, V>: The schema uses ancestors: <Container, ...>. Does this imply that Hash is effectively inheriting from Container<Tuple<K, V>>?
    Because I don’t think it is possible to inherit non concrete or non instantable type as Container<Any>.

  • Multiplicity_interval: This inherits from ancestors: <Proper_interval,...>. Given that Multiplicity_interval is “An Interval of Integer, used to represent multiplicity, cardinality and optionality in models” (according to Foundation Types Spec) and Multiplicity_interval itself is simple class so shouldn’t its ancestor be closed, so should the BMM schema explicitly describe the generic specialization, or is the “closed” nature implied by its definition?

3. Defining Override and Redefinition Rules

To support a robust semantic analyzer, that can be of use for later if there’s plans on support extensions of BMM like addition of standard library or custom developed bmm files. I’ve been mapping out the rules for property and routine overrides. I’m leaning towards an Eiffel-inspired approach but wanted to confirm if these align with the openEHR roadmap for BMM extensibility:

  1. Properties: Covariant overrides allowed.

  2. Statics: Assume no overrides/redefinitions permitted?

  3. Routines: Covariant signatures (arguments/return types).

  4. Uniform Access (Function to Property): Observed in DV_QUANTIFIED (magnitude: Function → Real property). Is the “reverse” (Property to Function) strictly disallowed to protect assignment semantics?
    according to quoting from Bernard Meyer’s OOSC ch14.6

    Combined with polymorphism and dynamic binding, such redeclarations of routines into attributes carry the Uniform Access principle to its extreme.

    Not the other way around
    You might expect to be able to redefine an attribute into an argumentless function. But no.
    Assignment, an operation applicable to attributes, makes no sense for functions.

  5. Diamond/Multiple Inheritance conflicts: in Contrast to Eiffel which it forces you to make an explicit design decision using qualifers like select and redefine and other clauses. BMM is simpler, I would imagine that such conflicts are not allowed in the first place? (note: I didn’t see such conflicts in current BMM files to my knowledge).

    same thing for diamond inheritance, e.g some classes A, B inherit feature foo from Root and both refedine it it in a different matter, and then class GrandChild wants to inherit (A, B), in such case which features should be inherited, or is this disallowed ??

  6. Implicit inheritance from Any: since Bmm is a unified type system where every class inherits `Any` (similar idea to Java, Eiffel), some classes can explicitly inherit it like `Ordered` and other that don’t like `DATA_VALUE`(inherits definition class that ultimately doesn’t inherit Any directly).
    so even implicit inheritance can access Any features likeis_equal(other) but their implementation and the strategy of dispatch is left to implementers. and the problems of diamond problem that arise due to this single Root class, this can be left to implementers decision.

    Question: are there plans to add redeclaration rules to the specs ?

I apologize if these seem like broad questions—my goal is simply to build a consistent “computational contract” for these models. I’d be interested in hearing others opinions and how they handle these edge cases in their own implementations.

Hi Mohammed,

I can answer many of those questions, but remind me of what language your implementation is in, and also what you want to do with it - i.e. is it to work with existing BMM files, or to do new work?

If you are interested in the future, you might want to have a look at my new implementation, which makes quite a lot of the choices you ask about (and fixes some errors in the spec). It doesn’t get import the older syntax files though, because it’s based on a new native syntax. So it might or might not be of use to you.

Hi Mr. Beale,
Thank you for your response.
I am trying to implement BMM in C++, the idea is to achieve a perfect mapping between the implementation and semantics of OpenEHR(muti inheritance and different feature overriding strategy than most production OOP languages, Design by contract, ,etc), so I am trying to implement openehr as a scripting language (a Domain specific one) that mainly use in-memory BMM as reflection model for serialization and validation and running simple expression. the end goal is to drop the need to update implementation every change in the spec, the the workflow would be as following :
.bmm files => loaded to in-memory Meta Model(BmmModelAccess BmmModel, BmmClass, etc.) => from there you have two options

  1. you can use it as AST or intermediate representation and run an Interpreter (can be used for demo or dev time).
  2. compile it using LLVM for production to achieve maximum performance.

-So both options would at the end depend on the Meta model (BMM) , as many language call it Refections or Metaprogramming.

  • so the schema kind doesn’t matter(i think the bmmv4 is better though) as much as the in-memory representation(to be an expressive one), to calculate memory layout of each created instance and polymorhic dispatch for functions and even properties setters(by building vtable-like functionality).

-But I know that most implementation are Java, Kotlin, C#. but all I need is the ideas, to reach a semantically valid & complete MetaModel to start from there. That is why am asking about things like how container ancestor represened and redeclaration rules (semantics of in-memory BMM represention)

May I ask where I can see the new implementation?

When it’s a bit more solid, it’ll will appear in a public Git repo.

I’d be thankful if I can have look at the new implementation,
Will it have a seperate spec documentation? and will it be a build upon BMMv3 spec ?
and if there’s a draft for that, can I have a view access too?
Looking forward to your great work.

My email: meezozawahra@gmail.com
github: mezozawahra · GitHub

These classes are themselves generic classes in the system. Types like List<Item> are instances of BMM_CONTAINER_TYPE, which is defined as:

|
| Meta-type that specifies linear containers with a generic parameter corresponding to the
| type of contained item, and whose container type is a generic type such as `List<T>`,
| `Set<T>` etc.
|
class Bmm_container_type
    is_a Bmm_type

feature_group ("access")

    |
    | The type of the container. This converts to the `root_type` in `Bmm_generic_type`.
    |
    prop container_class : Bmm_generic_class ;

    |
    | The container item type.
    |
    prop item_type : Bmm_unitary_type ;

    |
    | etc
    |

end

Now if you look at List<T> you find (I include only a couple of interface functions):

|
| Ordered container that may contain duplicates.
|
builtin class List<T>
    is_a Container<T>

feature_group("attributes")

    |
    | Return first element.
    |
    func first: T
        pre_cond
            not_empty: not is_empty ;
        ;

    |
    | Return last element.
    |
    func last: T
        pre_cond
            not_empty: not is_empty ;
        ;

end

Here, the List<T> is a class name, not a type name, so it is parsed as a Bmm_generic_class.

In BMML, there is no longer any need to do anything complicated syntactically, you just do it like in Java or C++, see above as an example.

I can’t say what the SEC wants to do in the future, but it would be reasonable to make any future version of BMM such that models can fairly easily map to mainstream languages. Note that there will always be some transformation rules needed, since C++, Java, Rust, and (say) Python have quite different ideas of what is allowed in inheritance.

In SPLASH, there is a separate version of BMM, which we will release openly, when we get the documentation into shape, so that there is an open definition of BMML and the more advanced version of the meta-model (that handles full routine, expression, statement definition and so on). In that version, BMM does allow covariant type redefinition in the way Eiffel does. However, one can create a particular BMM model that does not use such redefinition. In other words, BMM is more powerful than some of the models written in it. It is up to model authors to decide what they want to do.

BMM4 doesn’t allow redefinition of routines into attributes however.

I have not worried about the diamond inheritance question, because it rarely / never comes up in a good design. Eiffel libraries historically have arguably overused inheritance, and the same effects can be achieved in other ways. BMM doesn’t try to solve all these mostly academic edge cases.

Implicit inheritance from Any is always assumed if not stated. It’s just a convenience that allows writers to be a bit lazy, and not be punished for it.

In the SPLASH version, yes.

First of all, Thank you for your time, it has much clearer synatx.
1)
regarding the List<T>: Container<T>, I know that the class itself will be a BMM_GENERIC_CLASS, and that type that are instantiated from it are BMM_CONTAINER_TYPE, my question was how to represent BMM_GENERIC_CLASS.ancestors: Hash<String, BMM_MODEL_TYPE>, meaning after parsing List<T> to in-memory BMM_GENERIC_CLASS as the following:

BMM_GENERIC_CLASS {
    name: "List", 
    ancestors: {
          "Container<T>": {} // BMM_CONTAINER_TYPE not allowed ? should it be BMM_GENERIC_TYPE ??
    }
    generic_parameter: { 
          "T": BMM_PARAMETER_TYPE {
               name: "T"
          }
    }
     // features, invariants , etc.
}

as you can see above, how should the ancestor Container<T> be described? since the ancestor should be described as BMM_MODEL_TYPE which is BMM_UNITARY_TYPE, so my question it possible to describe the ancestor Container<T> as BMM_GENERIC_TYPE here as an exception rather than BMM_CONTAINER_TYPE?

and that question also holds for Hash<K, V> : Container<??> , in that case also what is best way to describe the ancestor inside Hash<K, V> declaration

I am sorry if I didn’t explain it better earlier.


2)

but it would be reasonable to make any future version of BMM such that models can fairly easily map to mainstream languages. Note that there will always be some transformation rules needed, since C++, Java, Rust, and (say) Python have quite different ideas of what is allowed in inheritance.

While it sounds promising that the specification prioritizes easy mapping to various programming languages, I feel this approach introduces unnecessary constraints. By trying to accommodate as many target language, it would risk failing to achieve a truly comprehensive mapping for any of them.

A more compelling alternative is for the specification to define itself as a self-contained, simple language with its own distinct rules and semantics, which can then can be compiled down to a lower-level implementation. OpenEHR already has a significant head start in this direction with BMM and EL.
For example, a BMM4 file containing class definitions, such as:

Person: { 
      name: String, 
      age: Integer 

      invariants: {
           valid_age: age >0
           valid_name: !name.empty()
      }
} 

would be loaded and compiled into low-level machine layout structures, like this:

fields: [
  { name: "name", size: 8 bytes, machineType: Ptr, offset: 0 },
  { name: "age", size: 4 bytes, machineType: int32, offset: 8 }
] // => instance size = 12 bytes

Under this architecture, any validation or deserialization logic would rely on uniform BMM semantics, regardless of the host language (C/C++, Java, etc.), while maintaining this exact memory layout.
If the specification introduces new classes, they would simply be added to the BMM schema for the runtime engine to load dynamically. Actual programmer intervention would only be required if a specific function demands custom internal implementations. Ultimately, this paradigm frees the specification from being bottlenecked by the obligations of target languages.
That is precisely why I believe leveraging BMM in this way would elevate OpenEHR to the next level. Consequently, it was highly disappointing to see that development in this direction has been paused.
Therefore I’ll eagerly wait for SPLASH.

Apologies, I read your original too quickly. That Container<T> is parsed as a `BMM_GENERIC_TYPE`.

You can see in the Archie code.

Generally speaking, I would suggest to see what is going on in Archie to get an idea of how things are being parsed and represented.

I agree with you. We don’t impose any constraints on BMM4 (well, we do, but only meaningful designed ones); instead, to map to most programming languages, there are ‘pattern converters’ that know how to convert e.g. covariant redefinition into Java, versus e.g. Python. Most of these target languages will be less sophisticated than BMM itself. But any community using BMM can just choose not to use all its features, just as you can write C++ without any template types. @borut.jures has done a lot of this kind of stuff, generating different types of code.

You are using a very C/C++ oriented meta-model there!

yep , this very similar to how Java language is handled, each .java file is compiled to formalism similar to above, and then when loaded, it builds its own meta model from it and exposes it using Reflection API, for example you can read what each class fields are i.e cls.getDeclaredFields(): Field[] (for more details on Field), also it offers extensiblity using and the libraries using decorators(using @decorator() syntax adds metadata to the meta-model under the hood), and that is how libraries like Jackson use it to serialize, deserialize and validate json, they rely heavily on Java’s built-in Reflection API instead of code generation(i.e generating JsonSchemas).
That is why, I find it more convenient and freeing to use the same approach Jackson uses with Java and apply it on Openehr and BMM.

Thank you for your time, and I am anticipating your work in SPLASH.