DeepRuby: Extending Ruby with Dual Deep Instantiation Bernd Neumayr Christoph G. Schuetz Department of Business Informatics – Data & Department of Business Informatics – Data & Knowledge Engineering Knowledge Engineering Johannes Kepler University Linz Johannes Kepler University Linz Linz, Austria Linz, Austria bernd.neumayr@jku.at christoph.schuetz@jku.at Christian Horner Michael Schrefl Department of Business Informatics – Data & Department of Business Informatics – Data & Knowledge Engineering Knowledge Engineering Johannes Kepler University Linz Johannes Kepler University Linz Linz, Austria Linz, Austria michael.schrefl@jku.at Abstract—Clabjects, the central construct of multi-level mod- ultimate instance of the clabject or property. For example, eling, overcome the strict separation of class and object in con- clabject CarModel with potency 2 is instantiated by BMW Z4 ceptual modeling. Ruby, a dynamic object-oriented programming with potency 1 which is in turn instantiated by Peter’s Car language, similarly treats classes as objects and thus appears as a natural candidate for implementing clabject-based modeling with potency 0. Clabject CarModel defines a property engine constructs. In this paper we introduce DeepRuby, a Ruby with potency 2 and target EngineModel which is instantiated implementation of the core constructs of Dual Deep Instantiation: by BMW Z4 has engine EngineK5 and in turn by Peter’s Car clabject hierarchies and attributes with separate source potency has engine Engine123; Engine123 is an instance of EngineK5 and target potency. DeepRuby represents clabjects at two layers: which is an instance of EngineModel. Clabject CarModel the clabject layer and the clabject facet layer. At the clabject facet layer, a clabject with maximum source potency i-1 and further defines a property listPrice with target currency value maximum target potency j-1 is represented by a matrix of i × j and potency 1 which is instantiated by BMW Z4 has list price clabject facets organized using Ruby’s superclass and eigenclass e 42,232. constructs. Clabject facets can easily be extended with behavior Dual Deep Instantiation [9] (DDI) allows to specify the implemented in custom methods. number of instantiation steps separately for the source and for Index Terms—Object oriented programming, Metamodeling the target of a property. For example, clabject CarModel, as source, introduces property owner with source potency 2 and I. I NTRODUCTION target Person with target potency 1. This property is ultimately Object-orientation is arguably the most important paradigm instantiated between instances of instances of CarModel as in programming and conceptual modeling. Statically-typed source and instances of Person as target, for example by Peters object-oriented programming languages, like Java, and tradi- Car has owner Peter. Clabject CarModel further has a self- tional conceptual modeling approaches, like E/R and UML, describing property creator with source potency 0 and target come with a strict separation between class and object. The Person and target potency 1. This property is instantiated clabject as central construct of multi-level modeling [10] over- between CarModel as source and an instance of Person as comes this separation and not only plays the roles of class and target, for example by CarModel has creator Peter. object but also of metaclass, potentially at many classification In previous work, we formalized different variants of DDI levels. Extending traditional modeling/programming languages in deductive database languages, namely F-Logic [9] and Con- to supporting clabjects is difficult, due to this inherent mis- ceptBase [11], but without support for implementing behavior. match. Dynamically typed languages like Ruby overcome the In this paper we introduce DeepRuby, an implementation of strict separation between object and class: classes are also DDI in Ruby that supports the implementation of custom treated as objects and may be extended at runtime. Based on methods. DeepRuby makes heavy use of Ruby’s dynamic this kinship, Ruby suggests itself as a suitable language for programming and metaprogramming facilities [12]. The Deep- implementing multilevel modeling constructs. Ruby version presented in this paper only implements a subset Deep Instantiation [2] is one of the most prominent ap- of DDI: it does neither support clabject generalization nor proaches to multi-level modeling. A potency assigned to a multi-valued attributes. These simplifications allow to set the clabject or property indicates the number of instantiation focus on the following core idea of DeepRuby. levels, i.e., the number of instantiation steps to reach the DeepRuby implements DDI at two layers, the clabject 1 module Social Module 2 class Person 3 attr_accessor(:age) eigenclass attr_accessor() 4 def initialize(a) superclass 5 self.class.incCounter 6 @age = a; end class Class 7 end 8 class << Person 9 def counter; @counter; end 10 def incCounter BasicObject #BasicObject 11 if @counter.nil?; @counter = 0; end 12 @counter = @counter + 1; end 13 end 14 class Woman < Person; end Object 15 Mary = Woman.new(31) #Object 16 Mary.age = 27 instance_ variables() 17 puts Mary.age # => 27 inspect2() 18 puts Person.counter # => nil 19 puts Woman.counter # => 1 20 class << Mary 21 attr_accessor(:name) Person 22 end #Person 23 Mary.name = "Maria" age() age=() counter() 24 end initialize() incCounter() 25 class Object 26 def inspect2 27 str = "(#{self}" Woman 28 instance_variables.each {|x| str = str + #Woman 29 " #{x}=#{instance_variable_get(x)}"} @counter = 1 30 str += ")"; end 31 end 32 puts Social::Person.inspect2 #Mary 33 # => (Social::Person) Mary:Woman 34 puts Social::Woman.inspect2 @age = 27 name() 35 # => (Social::Woman @counter=1) @name =„Maria“ name=() 36 puts Social::Mary.inspect2 37 # => (# @age=27 @name="Maria") Fig. 1. Introductory example to Ruby’s object model: Ruby code and custom graphical representation of Ruby objects. Predefined objects depicted in grey layer and the clabject facet layer. DeepRuby’s clabject facet a small but intricate example (see Fig. 1). layer makes explicit each of the otherwise implicit facets Ruby’s modules provide a namespacing mechanism for of a DDI clabject by a flat Ruby object. For example, constants, such as class names. Class Person (see line 1:2, clabject facet CarModelˆ(2,1) with property owner=Person that is line 2 in the listing in Fig. 1) is created within represents the instance-instance-type of CarModel. Clabject module Social and can be accessed outside the module by facet CarModelˆ(0,1) with property creator=Person represents qualified name Social::Person (line 1:32). Note: method the self-type of CarModel. CarModelˆ(0,0) with property puts writes a string representation of the given object to an creator=Peter represents the self-value of CarModel. Clabject IO stream – for illustration, the actual output of the Ruby facet CarModelˆ(2,2) with property engine=EngineModel rep- program is given in the program as a comment (e.g., # => resents the instance-instance-metatype of CarModel. Clabject (Social::Person)). facet CarModelˆ(1,1) with property listPrice=CurrencyValue Member attributes of a Ruby class are defined as getter represents the instance-type of CarModel. and setter methods that access instance variables. Instance In the remainder of the paper, we give, in Sect. II, an variables are created when set by a method. To avoid the need introduction to Ruby’s object model. In Sect. III, we introduce to write getters and setters by hand, class Module provides a more intricate example DDI model and its representation a method attr_accessor that creates getter and setter in DeepRuby. We also explain the clabject naming scheme methods for an attribute of a given name. For example, in line typically used with DDI for clabjects with more than two 1:3, class Person (an instance of Class which inherits from instantiation levels. Sect. IV explains the clabject facet layer. class Module) calls attr_accessor for symbol :age to Sect. V exemplifies the extension of clabject facets with create setter method age= and getter method age in class custom methods. Sect. VI gives an overview of related work. Person to write and read instance variables @age (names Sect. VII concludes the paper with ongoing and future work re- of instance variables are marked by prefix ‘@’) of instances of garding the implementation of advanced constructs of DDI [9] class Person, such as Mary (see line 1:16 and line 1:17). and Dual Deep Modeling [11]. In Ruby, classes are treated as objects and can have instance variables themselves, called class instance variables. Classes II. BACKGROUND : RUBY ’ S O BJECT M ODEL are instances of class Class and also may have an eigenclass As a background for forthcoming sections this section (also referred to as singleton class). Methods defined with explains some relevant aspects of Ruby’s object model along a class’s eigenclass (also referred to as singleton methods 1 module SalesMgmt 2 p = DDI::Model.new(SalesMgmt,3) Product3 3 DDI::Clabject.new(p,1,:Person) categoryMgr1-1 = Person 4 Person.new(:MsBlack) owner3-1 = Person 5 Person.new(:Peter) 6 Peter.define(:spouse,0,1,Person) Category 7 Peter.ˆ(0,0).spouse = MsBlack Car2 Bike2 8 DDI::Clabject.new(p,2,:CarEngine) categoryMgr0-0 9 CarEngine.new(:EngineK5) = MsBlack categoryMgr0-0 engine2-2 = MsBlack 10 EngineK5.new(:Engine123) = CarEngine 11 DDI::Clabject.new(p,3,:Product) 12 Product.define(:categoryMgr, 1, 1, Person) 13 .define(:owner, 3, 1, Person) Model BMW Z41 BromptonM6L1 14 Product.new(:Bike) engine1-1 = EngineK5 15 Bike.new(:BromptonM6L) 16 Bike.ˆ(0,0).categoryMgr = MsBlack 17 BromptonM6L.new(:PetersBike) Individual PetersCar0 PetersBike0 18 PetersBike.ˆ(0,0).owner = Peter owner0-0 = Peter engine0-0=Engine123 owner0-0 = Peter 19 Product.new(:Car) 20 Car.define(:engine, 2, 2, CarEngine) 21 Car.categoryMgr = MsBlack 22 Car.new(:BMWZ4) 23 BMWZ4.ˆ(1,1).engine = EngineK5 Person1 24 BMWZ4.new(:PetersCar) favouriteItem1-3 = Product 25 PetersCar.ˆ(0,0).owner = Peter 26 PetersCar.ˆ(0,0).engine = Engine123 27 Person.define(:favouriteItem, 1, 3, Product) Individual 28 Peter.ˆ(0,0).favouriteItem = PetersCar MsBlack0 29 MsBlack.ˆ(0,1).favouriteItem = BromptonM6L favouriteItem0-1 = 30 puts Peter.favouriteItem.name BromptonM6L 31 # => PetersCar Peter0 32 puts PetersCar.ˆ(0,1).engine.name favouriteItem0-0 = 33 # => EngineK5 PetersCar 34 Product.getMembersN(2).each{|c| puts c.name } spouse0-1 = Person 35 # => BromptonM6L \n BMWZ4 spouse0-0 = MsBlack 36 end Fig. 2. Running example: A DeepRuby program (left) realizing a DDI model (right) or class methods) can be used to access a class’s class the direct or indirect superclass of all custom classes created instance variables. The eigenclass of a class has as super- in Ruby programs and also the superclass of class Module class the eigenclass of the class’s superclass. For example, and Class. A method added to class Object can thus be Person’s eigenclass (labelled #Person in the graphical called from any Ruby object (with Ruby classes being also representation) is opened by ‘class << Person’ at line Ruby objects). Method inspect2 (line 1:26) is defined with 1:8. Person’s eigenclass defines a getter method counter class Object; when invoked on an object, it creates a string (line 1:9) together with a method incCounter (line 1:10) consisting of the object’s name and its instance variables (see which is called (line 1:5) to increment the counter every lines 1:32–1:36). time a new object is created. Class Woman has superclass III. D UAL D EEP I NSTANTIATION IN RUBY – AN E XAMPLE Person (defined by ‘class Woman < Person’ at line 1:14) and, thus, #Woman (the eigenclass of Woman) has In this section DeepRuby is explained along the example superclass #Person (the eigenclass of Person). depicted and implemented in Fig. 2. Ruby’s modules are used Class instance variables really belong to the class (as an as namespacing mechanism. The clabjects of a DDI model object) and class methods are called in the context of a class are created within such a module/namespace. For example, object. For example, when calling Woman.new to create a module SalesMgmt (line 2:1) serves as namespace for a DDI new instance of class Woman the initializer defined with class model with depth 3 (line 2:2), i.e., a model with maximum Person (line 1:4) is called, incCounter is called in the source and target potencies of 3. context of class Woman setting instance variable @counter Creating clabject hierarchies. A DDI model consists of one of Woman to 1 (see comment in line 1:19) and not of Person or more clabject hierarchies. Every clabject hierarchy has one which remains undefined (see comment in line 1:18). root clabject. A root clabject has a fixed clabject potency Single objects may also have singleton classes with single- (specifying the number of instantiation levels beneath the root) ton methods. For example, Mary’s singleton class (opened at and typically has a name. For example, clabject Person (line line 1:20 by class << Mary and depicted as #Mary) de- 2:3) and clabject Product (line 2:11) are the root clabjects fines getter and setter methods for accessing instance variable in the SalesMgmt model and have a potency of 1 and 3, @name of Mary. respectively. Ruby allows to open existing classes to add additional Clabjects are instantiated by sending message new. The methods which then affect all direct and indirect instances of new clabject is in the same module as its class and has a the class. For example, class Object (opened at line 1:25) is potency 1 lower than its class. For example, clabject Person with potency 1 is instantiated by MsBlack (line 2:4) and by target potency 3, meaning that the range of favouriteItem Peter (line 2:5), which get potency 0. Clabject Product is given by the members of the members of the members of with potency 3 is instantiated by Bike (line 2:14) and by clabject Product. Car (line 2:19) which get potency 2. Querying clabject hierarchies and attributes. The values Naming clabjects. The names of clabjects in DDI models and (meta) types of a clabject’s attributes are queried by (such as in Fig. 2) may seem counter-intuitive. For example, sending the attribute name to the clabject facet which is one would typically consider a class named Car to be a identified by the clabject together with source potency and specialization (and not an instantiation) of class Product. target potency. For example, sending attribute name engine In the following we explain how to read such models and to PetersCar’s clabject facet with source potency 0 and tar- sketch the rationale behind this naming scheme. get potency 1 (line 2:32) returns the type of engine of It is sometimes argued that deep instantiation’s support for PetersCar, which is EngineK5, which is inherited from concise modeling comes with the price of lack of concep- BMWZ4. tual clarity [3]: one clabject may represent multiple domain For getting or setting attributes with source potency 0 and concepts which makes it more difficult to differentiate these target potency 0 it is not necessary to specify the clabject facet. different domain concepts. In order to make these different If a message is sent to a clabject it dispatches it to its 0-0 facet. domain concepts explicit, we proposed [9]–[11] to give mean- For example, when sending attribute name favouriteItem ingful names to instantiation levels of a clabject and to produce to Peter (line 2:30) it is dispatched to Peterˆ(0,0) and the name of an implicitly represented domain concept by retrieves Peter’s favourite item, which is his car. combining a clabject name with a level name. DeepRuby provides methods to navigate clabject hierarchies For example (see Fig. 2), clabject Product has instantia- to facilitate flexible querying of DDI models. For example, tion levels Category, Model, and Individual, representing do- line 2:34 retrieves the members of the members of Product, main concepts Product Category, Product Model, and Product these are BMWZ4 and Brompton. Individual. Clabject Car is an instance of Product Category DeepRuby provides (1) generic query mechanisms (1a) to and further represents domain concepts Car Model and Car retrieve attribute values and (meta) types including inherited Individual (which are specializations of Product Model and values and types (1b) to navigate clabject hierarchies and Product Individual). Clabject BMWZ4 is an instance of Car retrieve a clabject’s members at a specific level and (2) takes Model and further represents domain concept BMWZ4 Individ- care of keeping DDI models consistent when defining and ual (a specialization of Car Individual). Finally, Peters Car setting attributes, with regard to: (2a) correct number of is an instance of BMWZ4 Individual. instantiation steps at the source and the target, (2b) target clabjects are compatible with targets at higher potencies, (2c) Defining and instantiating attributes. Attributes are defined a newly introduced target does not produce type conflicts at with a source clabject, a name, a source potency, a target lower potencies and at descending clabjects. potency, and a target clabject. For example, clabject Product defines an attribute with name owner, source potency 3, target IV. D EEP RUBY UNDER THE H OOD potency 1, and target clabject Person (line 2:13). By freely combining source and target potencies, a clabject A clabject has many clabject facets, one for each com- c with maximum source potency m (given by the clabject’s bination of source potency and target potency. In order to potency) and maximum target potency n (given by the DDI set attribute engine at source potency 1 and target po- model’s depth) has (m + 1) × (n + 1) clabject facets. Every tency 1 at clabject BMWZ4 to EngineK5, one first selects such facet corresponds to a combination of source potency and the clabject facet (BMWZ4.ˆ(1,1)) to which one sends target potency. The basic idea of DeepRuby is to represent engine=EngineK5 (line 2:23). every such clabject facet as a ’flat’ Ruby object (which in Clabjects with potency 0 have no members, yet they may the current approach is always a class) with instance variables define attributes with a target potency higher than 0, similar to and methods. For example, clabject Car with potency 2 in a what can be accomplished in Ruby with singleton classes of model with depth 3 has 12 (3 × 4) clabject facets. The object an object (e.g., attribute name defined with Mary’s singleton Carˆ(0-0) holds @catMgr=MsBlack and Carˆ(2-2) class at line 21 in Fig. 1). For example, clabject Peter defines holds @engine=CarEngine as instance variable. The rela- an attribute spouse with source potency 0, target potency 1, tionships between clabject facets are represented using Ruby and target Person (line 2:6) and instantiates it with target constructs: potency 0 and target MsBlack (line 2:7). i,j • The eigenclass of clabject facet c is clabject i,(j+1) Root clabjects with clabject potency 1 are akin to ‘normal’ facet c . For example, the eigenclass of class classes in that they have individuals as members. They are Carˆ(0,0) is clabject facet Carˆ(0,1). different from normal classes in that their attributes may have • If clabject c is an instantiation of clabject d, then a range defined at a higher classification level. For example, every clabject facet ci,j has clabject facet d(i+1),j Person (line 2:3) has individuals MsBlack (line 2:4) and as superclass. For example, Carˆ(0,0) has super- Peter (line 2:5) as members, yet it defines an attribute class Productˆ(1,0) and Carˆ(0,1) has superclass favouriteItem (line 2:27) with target Product and Productˆ(1,1). eigenclass superclass target potency 0 1 2 3 level(0) Product3 Product Product^(0,0) Product^(0,1) Product^(0,2) Product^(0,3) 0 categoryMgr1-1 = Person owner3-1 = Person level(1) Product^(1,0) Product^(1,1) Product^(1,2) Product^(1,3) 1 @catMgr = Person catMgr() source potency catMgr();catMgr=() catMgr=() level(2) Product^(2,0) Product^(2,1) Product^(2,2) Product^(2,3) 2 level(3) Product^(3,0) Product^(3,1) Product^(3,2) Product^(3,3) 3 @owner = Person owner() owner() owner=() owner=() instantiation_of superclass superclass superclass superclass level(0) Car2 Car Car^(0,0) Car^(0,1) Car^(0,2) Car^(0,3) categoryMgr0-0 = MsBlack @catMgr = MsBlack engine2-2 = CarEngine level(1) Car^(1,0) Car^(1,1) Car^(1,2) Car^(1,3) level(2) Car^(2,0) Car^(2,1) Car^(2,2) Car^(2,3) @engine = CarEngine engine() engine() engine=() engine() engine=() engine=() instantiation_of superclass superclass superclass superclass 1 level(0) BMW Z4 BMWZ4 BMWZ4^(0,0) BMWZ4^(0,1) BMWZ4^(0,2) BMWZ4^(0,3) engine1-1 = EngineK5 level(1) BMWZ4^(1,0) BMWZ4^(1,1) BMWZ4^(1,2) BMWZ4^(1,3) @engine = EngineK5 instantiation_of superclass superclass superclass superclass level(0) PetersCar0 PetersCar PetersCar^(0,0) PetersCar^(0,1) PetersCar^(0,2) PetersCar^(0,3) 0-0 owner = Peter @owner = Peter engine0-0 =Engine123 @engine = Engine123 DeepRuby DeepRuby DDI Model Clabject Layer Clabject Facet Layer Fig. 3. Realizing clabject facet matrices in Ruby using superclass and eigenclass A clabject is first represented as an instance of class by BMWZ4ˆ(1,1) and in turn by PetersCarˆ(0,1). Clabject (line A:25 in the Appendix) with an array Target clabjects of attributes (represented as instance • levels which holds for each source potency a reference variables) are inherited from superclass ci+1,j to sublass to the respective instance of class ClabjectFacet (see ci,j (this is implemented generically as part of Deep- line A:210) with target potency 0, from there one can navigate Ruby). For example, when sending message engine to other clabject facets along eigenclass relationships. Sending to PetersCarˆ(0,1) one gets EngineK5, which is message ˆ(i,j) to a clabject c returns clabject facet ci,j . inherited from BMWZ4ˆ(1,1). A clabject facet’s attribute clabject (line A:213) allows What is the role of eigenclass relationships in Deep- to navigate back from clabject facet to clabject; for example, Ruby?: from clabject facet Carˆ(2,1) to clabject Car. • The eigenclass c i,j+1 of a clabject facet ci,j pro- What is the role of superclass relationships in Deep- vides methods for accessing instance variables of Ruby?: ci,j (this comes for free with the eigenclass con- i+1,j • Methods are inherited from superclass d to subclass struct). For example, setter method catMgr= de- i,j c (this comes for free, since this is what class hier- fined in Productˆ(1,2) is called for setting archies are traditionally used for). For example, setter @catMgr=Person in Productˆ(1,1) method engine= defined at Carˆ(2,1) is inherited • Target clabjects (represented as instance variables) at ci,j+1 act as constraint for target clabjects at ci,j Currency.ˆ(1,1)) (see line 4:4). Second-level mem- (this is implemented generically as part of DeepRuby). bers of Currency get getter and setter for attribute For example, target clabject engine=EngineK5 of value by invoking attr_accessor on the eigenclass of PetersCarˆ(0,1) (inherited from BMWZ4ˆ(1,1)) Currency.ˆ(2,0) (which is Currency.ˆ(2,1)) (line acts as constraint when invoking setter method engine= 4:7). Currencies Pound, Euro, and Yen are created with on PetersCarˆ(0,0). their isocode and exchange rate (lines 4:20–4:25). GBP38200 We have further been experimenting with two alternative is an instantiation of Pound (and a second-level member of representation of the clabject facet matrix: In the first alter- Currency) with value 38200 (line 4:26). native represenation, clabject facets with target potency 0 are Second-level members of Currency have a method represented as simple objects and not as classes. This would for pretty printing (defined with the eigenclass of also be a reasonable design choice since these facets do not Currency.ˆ(2,0) which is Currency.ˆ(2,1)), mak- act as classes, yet it makes the implementation a bit more ing use of value and isocode. In order to get the complex. isocode the method needs to first navigate from the clabject In the second alternative representation, we introduce an facet (instance of ClabjectFacet) to the corresponding additional layer between DDI-clabject layer and clabject facet clabject (instance of Clabject) along attribute clabject layer in order to align DeepRuby with DeepTelos [6]. At this and from there along attribute cclass to the corresponding intermediate layer, a DDI clabject is represented by multiple first-level member of Currency (line 4:9). Sending pretty simple clabjects. A simple clabject resembles an object, class, to GBP38200 results in ‘GBP 38200’ (line 4:27). metaclass, or metaˆn class in DeepTelos. Clabject facets with Second-level members of Currency further have a the same difference between source potency and target potency method toCurrency which takes a first-level member of are collected into such a simple clabject. For example, clabject Currency as parameter (line 4:11). Sending toCurrency facets Carˆ(0,0), Carˆ(1,1), Carˆ(2,2) are collected with parameter Euro to GBP38200 produces a new instanti- into a simple clabject Car 0 and clabject facets Carˆ(1,0), ation of Euro which is pretty printed as ‘EUR 42784.0’ (line Carˆ(2,1) are collected into a simple clabject Car 1, with 4:28). Car 0 having class Car 1 as its most-general instance. A The eigenclass of Yen.ˆ(1,0) (which is simple clabject combining facets where the target potency Yen.ˆ(1,1)) overwrites method pretty inherited is higher than the source potency (e.g., Productˆ(0,1), from Currency.ˆ(2,1)) to use unicode symbol U instead Productˆ(1,2), Productˆ(2,3)) cannot be directly of isocode JPY . The new instantiation of Yen created by represented in DeepTelos. sending toCurrency with parameter Yen to GBP38200 is In this section we have explained the basic principles of pretty printed as ‘U 5556363.64’ (line 4:33). DeepRuby’s implementation and use of Ruby’s eigenclass Clabjects UK and Japan instantiate Country and have construct to implement Dual Deep Instantiation and have local currencies Pound and Yen, respectively. Asking for sketched two alternative representations. The evaluation and the exchange rate of Japan’s local currency returns 0.0077 fine-tuning of these alternatives is subject to ongoing work. (line 4:39). Method priceInCountry (see line 4:42) of second- V. S IMPLE ATTRIBUTES AND C USTOM M ETHODS IN level members of Product takes a country as parameter D EEP RUBY and converts the listPrice of second-level instantiations of Product to the country’s local currency, returning a new Using classes/eigenclasses arranged in superclass hierar- instantiation of the given currency with the value being the chies for realizing the clabject facet matrix allows to use stan- result of the conversion. Sending priceInCountry with dard Ruby constructs to implement simple attributes (attributes parameter Japan to BMWZ4 (see line 4:48) returns a new with non-clabjects as range) and behavior (custom methods) clabject pretty-printed as ‘U 5556363.64’ (line 4:48). on top of the clabject facet matrix, and to specialize behavior (i.e., overwrite methods, add additional methods) along the VI. R ELATED W ORK clabject hierarchy. With the advent of multi-level modeling, the question of To demonstrate these features, the running example from multi-level model execution emerges. Melanee [1], DeepTe- Fig. 2 is extended in Fig. 4 with clabject hierarchies los [6], MetaDepth [8], DeepJava [7], and XModeler [4] are Currency with simple attributes for exchange rate (with modeling tools and frameworks that support model execution, Euro as reference currency), isocode and value, and Country each pursuing a different strategy with respect to supporting with a local currency. Clabject Product is extended with a model execution. Multilevel programming may also be real- listPrice and a method priceInCountry to convert ized in a type-safe manner by metaprogramming and reflective the list price to the local currency of the given country. constraints [5]. First-level members of Currency receive getters and The Melanee multi-level modeling tool [1] supports model setters for simple attributes exchRate and isocode execution through a service API and a plug-in mechanism. The by invoking standard Ruby method attr_accessor communication between modeling and execution environment on the eigenclass of Currency.ˆ(1,0) (which is can be realized using socket-based communication. Changes 1 module SalesMgmt 2 DDI::Clabject.new(Product.model,2,:Currency) 3 class << Currency.ˆ(1,0) 4 attr_accessor(:isocode, :exchRate) 5 end Currency2 6 class << Currency.ˆ(2,0) value2 7 attr_accessor(:value) exchRate1 8 def pretty isoCode1 9 "#{clabject.cclass.isocode} #{value}" pretty2() 10 end toCurrency2(Currency1) : Currency2 11 def toCurrency(c) 12 raise "#{c} is not a currency" \ 13 unless (c.isMemberN(1,Currency)) 14 obj = c.new Euro1 Pound1 Yen1 15 obj.value = (value * clabject.cclass exchRate = 1 exchRate = 1.12 exchRate = 0.0077 16 .exchRate / c.exchRate).round(2) isocode = „EUR“ isocode = „GBP“ isocode = „JPY“ 17 return obj pretty1-0() 18 end 19 end 20 Currency.new(:Pound); GBP382000 21 Pound.isocode = "GBP"; Pound.exchRate = 1.12; value= 42784.0 value= 5556363.64 value = 38200 22 Currency.new(:Euro) 23 Euro.isocode = "EUR"; Euro.exchRate = 1 24 Currency.new(:Yen) 25 Yen.isocode = "JPY"; Yen.exchRate = 0.0077 Country1 26 Pound.new(:GBP38200); GBP38200.value = 38200 localCurrency1-1 = Currency 27 puts GBP38200.pretty # => GBP 38200 28 puts GBP38200.toCurrency(Euro).pretty 29 # => EUR 42784.0 30 class << Yen.ˆ(1,0) UK0 Japan0 31 def pretty; "\u00A5 #{value}"; end localCurrency0-0 = Pound localCurrency0-0 = Yen 32 end 33 puts GBP38200.toCurrency(Yen).pretty 34 # => U 5556363.64 35 DDI::Clabject.new(Product.model,1,:Country) Product3 36 Country.define(:localCurrency,1,1,Currency) listPrice2-2 = Currency 37 Country.new(:UK).localCurrency = Pound 38 Country.new(:Japan).localCurrency = Yen priceInCountry2(Country1) : Currency2 39 puts Japan.localCurrency.exchRate #=> 0.0077 40 Product.define(:listPrice, 2, 2, Currency) 41 class << Product.ˆ(2,0) Car2 42 def priceInCountry(country) 43 listPrice.toCurrency(country.localCurrency) BMW Z41 44 end 45 end listPrice0-0 = GBP38200 46 BMWZ4.ˆ(0,0).listPrice = GBP38200 47 puts BMWZ4.priceInCountry(Japan).pretty 48 # => U 5556363.64 49 end Fig. 4. Custom methods: DeepRuby program (left) realizing a DDI model with methods (right) in the modeling environment then automatically reflect in the but comes with powerful metamodeling features unmatched execution environment, and vice versa. The execution envi- by DeepRuby. ronment can be implemented as a Java program. Concerning MetaDepth [8] is a text-based multi-level modeling frame- the definition of execution semantics, different approaches work with potency-based deep instantiation. MetaDepth is a exist. A “pragmatic” approach, for example, employs a Java Java-based implementation using a custom syntax. Among the representation of the multi-level model where each clabject in primary features of MetaDepth are multi-level constraints and the multi-level model corresponds to a single Java class, with derived attributes at different meta-levels. Execution semantics execution semantics defined using plain Java code. is defined using an OCL extension. MetaDepth provides an DeepTelos [6] extends the Telos metamodeling language interpreter for the thus defined multi-level models. MetaDepth and its implementation with “most general instances” to add also supports code generation complying to the Java Metadata support for deep instantiation. Since DeepTelos defines the Interface. extensions as a set of Datalog axioms, DeepTelos models are DeepJava [7] is an extension of the Java programming compatible with ConceptBase, an implementation of a Telos language with a mechanism for potency-based deep instan- variant. ConceptBase also allows for the definition of exe- tiation. Internally, a compiler transforms DeepJava code into cutable models using event-condition-action rules. In Sect. IV plain Java. Hence, each DeepJava class translates into a set we sketched how to group clabject facets into simple clabjects of Java classes, one for each clabject facet. The compiler also that resemble DeepTelos classes. DeepTelos does not directly generates code for clabject instantiation at runtime, which is support self-describing clabjects (i.e., clabject with attributes realized using Java’s reflective functions. Clabject instantiation where the target potency is higher than the source potency) results in the dynamic generation of a number of interfaces. As a limitation, direct access without getters and setters is [8] de Lara, J., Guerra, E.: Deep meta-modelling with MetaDepth. In: Vitek, restricted to attributes with potency values smaller than two. J. (ed.) TOOLS 2010. LNCS, vol. 6141, pp. 1–20. Springer (2010) [9] Neumayr, B., Jeusfeld, M.A., Schrefl, M., Schütz, C.: Dual deep instan- With respect to Java, Ruby’s eigenclass concept much better tiation and its conceptbase implementation. In: Jarke, M., Mylopoulos, suits the clabject philosophy of multi-level modeling. As J., Quix, C., Rolland, C., Manolopoulos, Y., Mouratidis, H., Horkoff, opposed to DeepJava, DeepRuby supports deep instantiation J. (eds.) CAiSE. Lecture Notes in Computer Science, vol. 8484, pp. 503–517. Springer (2014) with both a source and a target potency, resulting in the [10] Neumayr, B., Schuetz, C.G.: Multilevel modeling. In: Liu, L., Özsu, generation of a matrix of Ruby classes for each clabject. M.T. (eds.) Encyclopedia of Database Systems. pp. 1–8. Springer New York, New York, NY (2017) VII. C ONCLUSION [11] Neumayr, B., Schuetz, C.G., Jeusfeld, M.A., Schrefl, M.: Dual deep modeling: multi-level modeling with dual potencies and its formalization In this paper we introduced DeepRuby, a Ruby implemen- in F-Logic. Software & Systems Modeling pp. 1–36 (2016) tation of the core language constructs of Dual Deep Instantia- [12] Perrotta, P.: Metaprogramming Ruby 2. The Pragmatic Programmers (2014) tion [9]: clabject hierarchies and attributes with dual potencies. The system takes care of consistent instantiation of clabjects A PPENDIX and attributes and provides methods for querying multi-level D EEP RUBY I MPLEMENTATION models. Our experiences with implementing DeepRuby have 1 module DDI confirmed our initial conjecture that a dynamic programming 2 3 class Model language like Ruby that does not strictly separate classes and 4 attr_reader( 5 :depth, # maximum potency of user clabjects objects is a good platform for implementing clabject-based 6 :modul, # module/namespace for clabjects 7 ) modeling constructs. 8 def initialize(mod, n) 9 raise "initial maximum-depth too low (n < 1)" if n < 1 In an internal prototype we have also implemented DDI’s 10 @modul = mod 11 @depth = n advanced modeling constructs (which are missing from the 12 # create module-specific ClabjectLevel-class 13 # because of different eigenclass depth DeepRuby version presented in this paper): multi-valued prop- 14 cls = Class.new 15 modul.const_set(:ClabjectLevel, cls) erties, bi-directional properties, and clabject generalization. 16 # include module ClabjectFacet in eigenclasses 17 for n in (0..depth) The fine-tuning of the advanced prototype and experimentation 18 cls = cls.singleton_class 19 cls.send(:include, ClabjectFacet) with alternative representations of the clabject facet matrix is 20 end 21 end subject to ongoing work. 22 end # end Model 23 Dual deep modeling (DDM) [11], an extended version 24 25 class Clabject of DDI, additionally comes with multi-level cardinality con- 26 attr_reader( 27 :model, # reference to DDI model straints, property specialization hierarchies, and distinguishes 28 :levels, # array of clabject levels 29 :name, between property value and property range. Implementing 30 :instantiations, # first-level members 31 :instantiation_of, # class clabject these constructs in DeepRuby is subject to future work. 32 :potency 33 ) Moving beyond previous implementations of DDI/DDM in 34 35 def cclass ConceptBase [9] and F-Logic [11], DeepRuby allows to extend 36 instantiation_of 37 end clabject facets with custom methods. We have exemplified 38 39 def members the implementation of such methods and their inheritance and 40 instantiations 41 end specialization along the clabject facet hierarchy. 42 43 def ˆ(srcPtcy,tgtPtcy) 44 facet(srcPtcy,tgtPtcy) R EFERENCES 45 end 46 47 def facet(srcPtcy,tgtPtcy) [1] Atkinson, C., Gerbig, R., Metzger, N.: On the execution of deep models. 48 raise "Target potency #{tgtPtcy} above max potency #{model.depth}+1." In: Mayerhofer, T., Langer, P., Seidewitz, E., Gray, J. (eds.) Proceedings if tgtPtcy > (model.depth+1) 49 return levels[srcPtcy].eigenclassN(tgtPtcy) of the 1st International Workshop on Executable Modeling. CEUR 50 end Workshop Proceedings, vol. 1560, pp. 28–33. CEUR-WS.org (2015) 51 52 def initialize(model, potency, name=nil, parent=nil) [2] Atkinson, C., Kühne, T.: The Essence of Multilevel Metamodeling. In: 53 @name = name Gogolla, M., Kobryn, C. (eds.) Proceedings of the 4th International 54 @instantiation_of = parent 55 @potency = potency Conference on the UML 2001, Toronto, Canada. LNCS, vol. 2185, pp. 56 @model = model 19–33. Springer Verlag (Oct 2001) 57 @levels = Array.new(potency+1) 58 for m in (0..potency) [3] Carvalho, V.A., Almeida, J.P.A.: Toward a well-founded theory for 59 if parent.nil? multi-level conceptual modeling. Software & Systems Modeling (2016) 60 @levels[m] = createClabjectLevel(model 61 .modul.const_get(:ClabjectLevel), m) [4] Clark, T., Gonzalez-Perez, C., Henderson-Sellers, B.: A foundation for 62 else multi-level modelling. In: MULTI 2014. CEUR Workshop Proceedings, 63 @levels[m] = createClabjectLevel(parent 64 .levels[m+1], m) vol. 1286, pp. 43–52. CEUR-WS.org (2014) 65 end [5] Draheim, D.: Reflective constraint writing - A symbolic viewpoint of 66 end 67 @instantiations = Array.new modeling languages. Trans. Large-Scale Data- and Knowledge-Centered 68 model.modul.const_set(name, self) if name Systems 24, 1–60 (2016) 69 return self 70 end [6] Jeusfeld, M.A., Neumayr, B.: DeepTelos: Multi-level modeling with 71 most general instances. In: Comyn-Wattiau, I., Tanaka, K., Song, I., 72 def new(name=nil) 73 obj = Clabject.new( Yamamoto, S., Saeki, M. (eds.) ER 2016. LNCS, vol. 9974, pp. 198– 74 self.model, self.potency-1, name, self) 211. Springer (2016) 75 instantiations << obj 76 return obj [7] Kuehne, T., Schreiber, D.: Can programming be liberated from the two- 77 end level style: Multi-level programming with DeepJava. In: Proceedings 78 79 of the 22nd Annual ACM SIGPLAN Conference on Object-oriented 80 def createClabjectLevel(supercls,levelNr) Programming Systems and Applications. pp. 229–244 (2007) 81 cls = Class.new(supercls) 82 facet = cls 189 facet(srcPtcy,tgtPtcy).send("#{attribute}=", value) 83 for n in (0..model.depth) 190 return self 84 facet.clabject = self 191 end 85 facet.tgtPtcy = n 192 86 facet.srcPtcy = levelNr 193 def get(attribute, srcPtcy, tgtPtcy) 87 if(n < model.depth) 194 facet(srcPtcy,tgtPtcy).send(attribute) 88 facet = facet.singleton_class 195 end 89 end 196 90 end 197 def getValueSettingObject(attribute, srcPtcy, tgtPtcy) 91 return cls 198 facet(srcPtcy,tgtPtcy) 92 end 199 .getValueSettingObject(attribute.to_s.to_sym) 93 200 end 94 def to_s 201 95 name 202 def getMethodDefiningClass(attribute, srcPtcy, tgtPtcy) 96 end 203 facet(srcPtcy,tgtPtcy) 97 204 .method("#{attribute.to_sym}").owner 98 def getMembersN(n) 205 end 99 if n == 0 206 100 return [self] 207 end # end Clabject 101 elsif n == 1 208 102 return instantiations 209 103 elsif n > 1 210 module ClabjectFacet 104 tempAry = [] 211 105 ary = [self] 212 attr_accessor( 106 for m in (1..n) 213 :clabject, 107 ary.each do |cbj| 214 :srcPtcy, 108 tempAry = tempAry + cbj.instantiations 215 :tgtPtcy 109 end 216 ) 110 ary = tempAry 217 111 tempAry = [] 218 def to_s 112 end 219 clabjectname = (clabject.respond_to?(:name))? clabject.name : clabject 113 return ary 220 "#{clabjectname}ˆ(#{srcPtcy},#{tgtPtcy})" 114 end 221 end 115 end 222 116 223 def parent 117 def isMember(cbj) 224 superclass 118 if self == cbj 225 end 119 true 226 120 elsif self.respond_to?(:instantiation_of) && 227 def eigenclass !self.instantiation_of.nil? 228 singleton_class 121 self.instantiation_of.isMember(cbj) 229 end 122 else 230 123 false 231 def inherited(attribute) 124 end 232 if parent.respond_to?(attribute.to_s.to_sym) 125 end 233 parent.send(attribute.to_s.to_sym) 126 234 else 127 def isCompatibleWith(cbj) 235 false 128 return self.isMember(cbj) || cbj.isMember(self) 236 end 129 end 237 end 130 238 131 def isMemberN(n, cbj) 239 def getValueSettingObject(attribute) 132 if n == 0 and self == cbj 240 if instance_variable_get("@#{attribute.to_sym}") 133 true 241 self 134 elsif self.respond_to?(:instantiation_of) && 242 else !self.instantiation_of.nil? 243 getInheritedValueSettingObject(attribute) 135 self.instantiation_of.isMemberN(n-1, cbj) 244 end 136 else 245 end 137 false 246 138 end 247 def getInheritedValueSettingObject(attribute) 139 end 248 if parent.respond_to?(attribute.to_s.to_sym) 140 249 parent.getValueSettingObject(attribute.to_s.to_sym) 141 def method_missing(method, *args) 250 else 142 if levels[0].respond_to?("#{method}", *args) 251 false 143 levels[0].send("#{method}", *args) 252 end 144 else 253 end 145 raise NoMethodError.new("There is no method called #{method} here") 254 146 end 255 def getMostSpecific(attribute) 147 end 256 getMostSpecificN(attribute)[:val] 148 257 end 149 def define(attribute, srcPtcy, tgtPtcy, value) 258 150 for n in (1..(tgtPtcy+1)) 259 def getMostSpecificN(attribute) 151 obj = facet(srcPtcy,n) 260 if respond_to?(attribute) 152 #crete getter 261 val = self.send(attribute) 153 obj.class_eval(" 262 if val 154 def #{attribute} 263 return {:ptcy => 0, :val => val} 155 if @#{attribute} 264 elsif eigenclass.respond_to?(attribute) 156 @#{attribute} 265 x = eigenclass.getMostSpecificN(attribute) #recursion 157 else 266 if x[:val] 158 inherited(’#{attribute}’) 267 return {:ptcy => x[:ptcy]+1, :val => x[:val]} 159 end 268 end 160 end" 269 end 161 ) 270 end 162 #create setter 271 return {:val => false} 163 obj.class_eval(" 272 end 164 def #{attribute}=(val) 273 165 if valueSettingAllowed(:#{attribute}, val) 274 def valueSettingAllowed(attribute, value) 166 @#{attribute} = val 275 if value.kind_of?(Clabject) 167 end 276 ms = getMostSpecificN(attribute) 168 end" 277 if ms[:val] && !value.isMemberN( ms[:ptcy], ms[:val] ) 169 ) 278 raise "#{value.name} is not memberN(#{ms[:ptcy]}) of 170 end #{ms[:val].name}" 171 set(attribute, srcPtcy, tgtPtcy, value) 279 end 172 return self 280 return true 173 end 281 else 174 282 raise "#{value} is no Clabject" 175 def checkDownwardCompatibility(attribute, srcPtcy, tgtPtcy, value) 283 end 176 return true unless levels[srcPtcy].getMostSpecific(attribute) 284 end 177 return true if value.nil? 285 178 for potency in (0..srcPtcy) 286 def eigenclassN(n) 179 getMembersN(potency).each do |cbj| 287 obj = self 180 actMsVal = cbj.levels[srcPtcy-potency] 288 for m in (1..n) # returns self if n=0 181 .getMostSpecific(attribute) 289 obj = obj.singleton_class 182 raise "#{value.name} is not compatible with #{actMsVal.name}" if 290 end !value.isCompatibleWith( actMsVal ) 291 return obj 183 end 292 end 184 end 293 185 end 294 end # end ClabjectFacet 186 295 187 def set(attribute, srcPtcy, tgtPtcy, value, doDownwardCheck = true) 296 end # end module DDI 188 checkDownwardCompatibility(attribute, srcPtcy, tgtPtcy, value) if doDownwardCheck