During the OCL Workshop at MoDELS 2016, Daniel Calegari and Marcos Viera presented the idea of using the functional paradigm for the interpretation of OCL expressions (full paper available here).
The idea generated plenty of discussions so I invited them to expand on the concept of OCL Monads, the functional interpretation of OCL and its benefits. In what follows, we briefly resume the idea and provide some insights on its benefits. Enter Daniel and Marcos.
Since its origins, the OCL was defined as a declarative side-effect free language, combining model-oriented and functional features, e.g. type inheritance and functions composition, respectively. In the last few years, other functional features were proposed for their inclusion in the new OCL version, e.g. pattern matching, lambda expressions, and lazy evaluation. In this context, a functional approach comes as a reasonable alternative for exploiting such features. As a first step, we explored the use of Haskell as an interpreter for OCL with respect to its use for expressing invariant conditions in models, in the context of the Model-Driven Engineering paradigm.
Functional Representation of (meta)models
The functional interpretation of OCL requires, first, to tackle the functional representation of metamodels and models, which introduces some sort of model-functional impedance mismatch. Metamodels are represented as data types, and models conforming those metamodels are values of the type representing the metamodel. However, functional data types are tree-like structures, thus, each model element needs to have an identifier, so that references between elements (in a graph-like fashion) are expressed using such identifiers.
Without delving into details, the next fragment shows an excerpt of a functional representation of a metamodel in which there is an UMLModelElement with two attributes kind and name, an Attribute with two references to a Classifier and a Class. Each element is represented as a data type with their corresponding attributes. Moreover, the superclass contains an identifier oid, and references are expressed based on the identifiers.
-- UMLModelElement(oid:int, kind:String, name:String)
data UMLModelElement = UMLModelElement Int String String
-- Attribute(typ:Classifier, owner:Class)
data Attribute = Attribute Int Int
Functional interpretation of OCL
The complex part of the interpretation comes when dealing with model-oriented features on OCL expressions (e.g. type casting and navigation through properties) which require accessing the information of a specific model, and to perform a chain of computations. Moreover, we have to deal with the four-valued logic behind OCL with the notion of truth, undefinedness, and nullity. These aspects were handled by defining an OCL Monad.
Within the functional paradigm, a Monad is basically a structure which represents computations in terms of values and sequences of (sub)computations that use these values. In particular, the Reader monad is a special kind of monad representing computations over a shared environment.
Our proposal is based on representing every OCL expression as a computation over a Reader monad such that the shared environment corresponds to the model over which the OCL expression is specified. The monadic operator bind, defining a sequence of computations, allows representing navigation through properties and functions, such that the result of a computation is linked to each subsequent computation within the monad. Since we represent the result of a computation with a special type dealing with the OCL four-valued logic, after each computation we have the OCL Monad with the shared environment unmodified and the corresponding value at the moment of the computation.
This solution allows a clean handling of errors and a precise definition of the four-valued semantics of OCL. Moreover, it allows a direct representation of the OCL invariants mimicking its structure in a clear way. Without delving into details, the next fragment shows that the OCL invariant and its corresponding functional version are very similar.
inv: self.oclIsTypeOf(Class) implies
self.oclAsType(Class).attribute->size() > 0
chk = context Classifier [inv]
inv self = (ocl self |.| oclIsTypeOf Class) `implies`
((ocl self |.| oclAsType Class |.| attribute |->| size) > oclVal 0)
Some final thoughts
This approach is exploratory. However, it provides some benefits.
First, although the functional infrastructure we need for expressing invariants could not be easily readable for an inexperienced user, some parts can be predefined (the whole OCL library is model-independent) and the other automatically generated (e.g. operations for accessing model properties).
Second, it is potentially extensible for other OCL uses, e.g. for the description of pre- and post-conditions on operations and for constraining and computing object values in the definition of model transformation.
Finally, we like to think about this approach not as a potential contender of industrial tools, but as a “sandbox” in which functional abstractions can be used in order to evaluate many proposals that the community already made. Moreover, we hope this can help with the migration of functional concepts to the model-driven world.
Featured image taken from the Yet Another Monad Tutorial in 15 Minutes