How to simply represent in OCaml some UML classes and associations at a METAMODEL level?

uml

#1

I try to specify in Ocaml a model initially expressed in UML, with some classes and the associations between them.

Important:
I’m talking about the metamodel level (and meta meta model level, for which is the same mechanism). This means that these class and association constructs are used to define a model such as UML which acts as a metamodel, then allows to create a model.

I’m no more a totally beginner in OCaml programming, but I’m also far from being an OCaml expert. So pls. apologize my mistakes.

Well, I’m stuck in the middle with that.

I will start with a quite simple question: how to simply represent Uml classes and especially the Uml associations in OCaml?

First, let’s try to mimic in OCaml an UML class+association model.

Basically, we have the Module and the Object systems for that.

1/ Class
An Uml class has a name, some (0…n) attributes (which have a type) and some (0…n) operations (which have a signature). And also relations with other classes (moreover, a navigation can be defined between 2 classes : zero, one or two directions).

With the OCaml Object system I think I can directly represent a class (name, type, methods) and I’m done. I can then instantiate objects. But I’m stuck at a class/object level which is not what I look for.

class umlClass =
  object
    val mutable name = "Class"
    val mutable att1 = "Attribute1"
    method get_name = name
    method set_name str= name <- str
    method get_att1= att1
    method set_att1 str = att1 <- str
    (* etc. *)
  end

With the module system, it seems easy. At first sight.
A record type seems to be a relevant construct for holding class name class attributes:

module FooClass = 
  struct 
   type  t = {mutable classname : string ;
              mutable att1 : string ;
              mutable att2 : string;
             }
   let get_name c = c.classname
   (* Uh-uh! It looks like this is a module that denotes an object but not a class *)

  let ope1 c a1 a2 = a1 + a2 (* becoming weird... (thinking of a module as a class) *)
  end

But in fact this module dos not represent a class: it represents a concrete object.
So the module system seems not adapted to that.

2/ Association between several "classes"
The general case in a n-ary association between some classes.
A binary association is a particular case. It can also be typed as an aggregation (a car has one engine and four wheels) or composition (a man has two hands).
Moreover, any association can be typed to describe its multiplicity (how many times an instance of a class is involved in this association, the role of a class in the association, and the navigability (“which class sees which classes”).

In the documentation of the Object system of OCaml, I see many interesting constructs for OOP: inheritance, multiple inheritance, polymorphic methods, etc. But I don’t see any construct for representing an association between some classes.
So I would naturally try to define tuples or record within the type of a class to create typed associations with other classes. It will certainly work but it looks a bit like reinventing the wheel.

In OCaml, what is the recommended way to represent (typed) associations between classes in OCaml? (that become links between instantiated objects)


Meta metamodel level
The meta metamodel is (mainly) based on a class and association. It can then be used to describe some UML-like models that will act as a metamodel (e.g. instead of simply having the classes Person and Car, and an association between Person and Car, we’ll have all classes stereotyped as “nice” or “bad” and associations stereotyped as “safe” or “unsafe”. Very simplified and inaccurate example).
Here is the problem I encounter when trying to manipulate classes and associations at the meta metamodel level in order to define a metamodel:

When specifying a model in OCaml, I can use concrete types:

   ...
   type  t = {mutable classname : string }
   let get_name c param= "Bla " ^ c.classname ^ param
   ...

But when coming to write a metaclass, I see no solution in Ocaml with the module system (I don’t say there are no solutions). I just believe I’m reinventing metamodeling tooling.

If I use the Object system, maybe I can define a class at meta metamodel level) with meta description (ownedElement, ownedEnd, packagedElement, etc…) of all structural features of such a class: class name, attribute names and types, operation names and types.
Meta names for (metameta)Class, (metameta)Attribute and (metameta)Operation can all be defined so their instances (and their own instances) have name with string type.
But how can I define the type of (metameta)Attribute so that its instances can have a type so its own instances have any valid OCaml type?
And how can I define the type of a (metameta)Operation so its instances can have a type so its own instances have any valid OCaml function type?

I have an idea for expressing these abstract types with symbols then using pattern matching for instantiating a class from the metaClass.
But it looks like I would reinvent the wheel. And I feel that I’m doing the same job as the OCaml type system.

How can we handle that meta considerations when programming with OCaml?
Can the type system be used/hacked to do this meta modeling job?

(*
Some example stuff to come here.
*)


#2

Here’s one way of modeling it:

module Attribute =
  struct 
         type name = string
         type value = string
         type t = (name * value) 
  end

module Uml_class =
  struct
    type times = int
    type role = string
    type relation =
      | Composition of t * role
      | Aggregation of t * times * role
    and t = {
      name: string;
      attributes: Attribute.t list;
      relations: relation list;
    }
    let set_name name t = { t with name }
  end

You can also make the types opaque with a module signature and provide getters and setters to enforce any invariants - https://dev.realworldocaml.org/files-modules-and-programs.html#designing-with-modules

(The type aliases (type name=string) are just for illustration and don’t give real type safety.)


#3

Thanks for your explanation about how to do object modeling with OCaml modules.

And how can you get instanciation facility with the module system? (class/object)

Have you any idea about how to handle the metamodel level?
I mean defining a metaclass and instantiate it in order to get an Ocaml module or class (forget about the meta metamodel level for now. It’s the same mechanism).

Thanks.


#4

I haven’t had a chance to use objects with OCaml yet; the consensus in the community is that there are only rare occasions when you need them and most things can be done with just modules and records.

OCaml has module functors - which are functions that takes input modules and returns new modules as the result. They are however not completely free-form: you have to specify the signatures of the output modules upfront.

I think you’ll need compile-time metaprogramming (https://whitequark.org/blog/2014/04/16/a-guide-to-extension-points-in-ocaml/) that can generate code according to arbitrary UML specs.

You can also go for plain code-generation like atdgen: https://atd.readthedocs.io/en/latest/tutorial.html. It takes a data specification that is very similar to OCaml type declarations, and generates OCaml code that can serialize and deserialize between multiple data formats.


#5

I see a disadvantage with using a module to hold a type (instead of simply using a type) because the module name should be used as a qualifier.
But what is the interest?

Basically, I believe that we can use a list or a record which allows naming:

type name = string 
type value = string (* aliases *)
type attribute = (name * value) 
type attribute = { name: string; value: string } 

Here is how I defined a simplified Uml class and association:

type uClass = { name: string;
                attributes: (string * string) list; (* (name, value) *)
                operations: string  list; (* name *)
              }

let cs1 = { name = "Person";
            attributes = ["Age", "8"; "Name", "Xu"];
            operations = ["run()"];
          }

let cs2 = { name = "Enterprise";
            attributes = ["Imm", "0123"; "Name ", "MyCompany"];
            operations = ["grow()"];
          }

type uAssociation = { name: string;
                     classes: (uClass  * string * int * int) list; 
                     (*       (Uml class, role, multiplicities) *)
                   }

let ass1 = { name = "first assoc"; 
             classes = [cs1, "cool", 0, 999; cs2, "nice", 1, 2] }

val ass1 : uAssociation =
  {name = "first assoc";
   classes =
    [({name = "Personn"; attributes = [("Age", "8"); ("Name", "Xu")];
       operations = ["run()"]},
      "cool", 0, 999);
     ({name = "Enterprise";
       attributes = [("Imm", "0123"); ("Name ", "MyCompany")];
       operations = ["grow()"]},
      "nice", 1, 2)]}

There is nothing very interesting here. It’s just coherent.
And I know that I’ll be able to navigate in the data structure even with a big number of classes and associations.

This does not answer yet to the meta question I first asked but it’s an opportunity to discuss about how to create association between values.

Concerning what is going on under the hood, do you know if OCaml is smart enough to define ass1 without reallocating memory blocks and yet just uses pointers to the values of cs1 and cs2?

I see only a product type (list, record) to correctly define an association between some stuff (uClass, objects, any OCaml values) in OCaml. Possibly hold in a variant as in your example.
is it correct or can it be simpler?


#6

It is. See the Javascript representation here. Since OCaml knows these are immutable values, it uses only the reference everywhere. I’m not sure how it works when the values are declared to be mutable.

I think either works, and it depends on how you plan to use the data. If you wanted to say generate UML diagrams from this data, it is straightforward - pattern match on the variants to generate appropriate graphics. For metaprogramming, you can traverse in a similar manner, but generate code instead of graphics.