~~NOTOC~~
^ Note: ^
| The description below discusses the current version of CoBro. This version has a new set of core domain concepts, a fully revised meta level interface, and a reworked tool suite. For a description of the original CoBro environment, refer to the [[https://ssel.vub.ac.be/c3/phd|PhD dissertation]] of Dirk Deridder. |
====== CoBro-CML: A Concept Manipulation Language ======
CoBro-CML is the Smalltalk language extension to manipulate concepts in the CoBro environment. It adheres to the syntax of Smalltalk and also uses message sending as the prime way to manipulate the Concepts. These are stored in a Concept Namespace which is introduced in the next section. This namespace provides all the functionality to create, modify, delete, and query concept definitions. Similar to Smalltalk it also provides a number of inspector-alike utilities that make it easier for the programmer to navigate through a concept network.
===== The Concept Namespace =====
Namespaces are used to disambiguate names. They provide a context where the object to which the name refers can be uniquely resolved. The standard VisualWorks Smalltalk namespace starts at the ''Root'' namespace, and stores the classes that make up the base system in the ''Root.Smalltalk'' namespace.
The dotted notation is used to describe a path to a binding for a particular name in the system. For example the full path to the ''OrderedCollection'' class is ''Root.Smalltalk.Core''. This path combined with the name of the class gives a //fully qualified reference// (e.g., ''Root.Smalltalk.Core.OrderedCollection'') that is unique in the system.
To be able to refer to concepts we created a dedicated ''Concepts'' namespace. Through this namespace, you can directly access concepts that are stored in the ConceptBase. As a result of namespace imports, the programmer can refer to concepts simply by using the prefix ''Concepts''. For example the binding for the ''Wine'' domain concept is looked up by evaluating the statement ''Concepts.Wine''. Note that since namespaces are dictionaries, an alternative way to access their contents is by sending the ''at:'' message (e.g., ''Concepts at: #{Wine}'').
A namespace definition can import bindings of other namespaces. Doing so allows the programmer to refer to them as if they reside in the local namespace (e.g., ''Concepts.OrderedCollection'' instead of ''Concepts.Root.Smalltalk.Core.OrderedCollection''). If you want to be able to use this 'shorthand' notation then you must import your namespace into the Smalltalk namespace. This can be done by evaluating the following statement (replace YourNamespace with the name of your namespace):
Root.Smalltalk
imports: Root.Smalltalk importString ,
'YourNamespace.*'.
As we will illustrate later on, implementation entities have a Concept-counterpart. This is needed so that domain knowledge described as concepts can be hooked up to the implementation. For example, if we want to access the concept associated to the ''OrderedCollection'' class, we can refer to it as ''Concepts.OrderedCollection''. Note that this is only possible because the Concepts namespace alters the regular binding lookup mechanism of Smalltalk. The process of creating a Concept-counterpart of an implementation mechanism is called //conceptification//. We will discuss this process later on.
===== Concept Creation =====
The creation of a concept can be done either:
- ex nihilo, or
- by using an existing concept definition, or
- by automatic conceptification, or
- by using one of the concept editors.
Note that there is a category-mechanism available which allows you to group concepts in an easy way.
== 1. Ex Nihilo ==
The ex nihilo creation of a concept corresponds to creating the concept's definition 'out of nothing'. It is done by sending the message ''defineConcept: aName displayName: aPrettyString superconcept: aSuperconcept'' to the ''Concepts'' namespace.
''aName'' is a unique name that will be used to identify the concept. It is similar to a fully qualified reference, and also uses the same syntax.
''aPrettyString'' is used for printing purposes and is typically a descriptive label for the concept.
''aSuperconcept'' identifies the parent concept of the one you are defining. The root of the concept world is ''Concepts.Concept''.
Consider for example the following definition of the ''GeographicalRegion'' concept:
Concepts
defineConcept: #{GeographicalRegion}
displayName: 'Geographical Region'
superconcept: Concepts.Concept
This way of defining a concept was inspired on how you define a new class in Smalltalk, which is done by sending the message ''defineClass: x superclass: y ...'' to the namespace in which you want to install the class. For example the class ''Person'' can be defined as follows in the ''Smalltalk'' namespace:
Smalltalk
defineClass: #Person
superclass: #{Core.Object}
indexedType: #none
private: false
instanceVariableNames: 'name age'
classInstanceVariableNames: ''
imports: ''
category: 'Person Example'
Note that CoBro contains a mnemonic generator to ensure the uniqueness of the names provided to identify a concept. If an existing name is chosen then the ''Concepts'' namespace will suggest an alternative:
{{ cobro:cobro:screenshots:mnemonicwarning.jpg?430 |A warning indicating that the name is already in use.}}
CoBro provides several alternatives to create a concept ex nihilo. We will not discuss these now since their usage only makes sense for particular situations (e.g. the creation of a transient concept).
To delete a concept you can send the ''remove: aConcept'' message to the ''Concepts'' namespace. For example:
Concepts remove: Concepts.GeographicalRegion
Note that the ''remove:'' method will verify if the concept has dependents (subconcepts or other concepts that are referring to it). If that is the case then a notification window is opened:
{{ cobro:cobro:screenshots:dependentswarning.jpg?430 |A warning that there are concepts depending on the existence of the one you are about to remove.}}
This is clearly only useful for interactive sessions, so there is also a ''removeSilently:'' which runs without notifications.
== 2. Creation by Using an Existing Concept Definition ==
Another way to create a concept is by reusing the definition of an existing one. Sending the message ''smalltalkTemplate'' to an existing concept, returns a generated textual definition of that concept. By editing the values in this template you can create a concept that is similar to the existing one. For example:
Concepts.GeographicalRegion smalltalkTemplate
returns
(Concepts
defineConcept: #{GeographicalRegion}
displayName: 'Geographical Region'
superconcept: Concepts.Concepts).
== 3. Creation by Automatic Conceptification ==
Creating a concept by conceptification is done by prefixing the reference of the code entity with ''Concepts''. An alternative way is to send the message ''asConcept'' to the code entity which is more useful if you want to conceptify the return result of a code query.
For example to access the concept counterpart of the ''OrderedCollection'' class you can type:
Concepts.OrderedCollection
or
OrderedCollection asConcept
Another example is to conceptify a return result of a message call that returns code entities. For example:
OrderedCollection withAllSubclasses
collect: [:each|each asConcept]
== 4. Creation by using a Concept Editor ==
There are several concept editors available that can be used to create or update a concept. The details of using the editors will be explained elsewhere. Here is a screenshot of the basic version of the concept editor:
{{ cobro:cobro:screenshots:concepteditor.jpg |Using a concept editor to create concepts.}}
== 5. Using categories when defining concepts ==
Quite often it is useful to have a grouping mechanism available for the concepts you define. Besides using it to structure your collection of concepts it also helps in managing your concept collections (e.g. to group a couple of temporary concepts). One way to introduce structure is to 'abuse' the superconcept relationship. For example you can 'group' utility relationships by having them point to the UtilityRelationship concept via the superconcept relationship. This way you can get access to all utility relationships by retrieving the subconcepts of UtilityRelationship. The main problem with this approach is that you abuse the semantics of the superconcept relationship (which indicates a 'parenthood' between the parent and the child). A better way is done by introducing a notion of categories. Categories are supported out-of-the-box in CoBro. They are not a special construct and have been introduced into CoBro in a soft way. The following are the main elements that were introduced to support a categorisation of concepts:
* ''Concepts.Category'' : This concept defines the notion of a category. Every concept that is intended to be used as a category should have the ''Category'' concept as its superconcept.
* ''Concepts.declaredMember'' : This relationship is used to identify the (declared) members of a category. In order to make a particular concept a member of a category you add a member-slot to it. In essence it corresponds to an extensional definition of members.
* ''Concepts.computedMember'' : This relationship is used to identify the (computed) members of a category. You provide a Smalltalk block that computes the members of a particular category. In essence it corresponds to an intensional definition of members.
* ''Concepts.members'' : This relationship returns a merged collection of both the declared and computed members of a category.
* ''Concepts.RootCategory'' : This is the category that will have as its members all the categories that exist in the ConceptNamespace. This way you can easily navigate to all the categories that exist (by sending the ''member'' message to it).
* ''Concepts defineConcept: x displayName: y superconcept: z category: c'' : This message to the Concepts namespace defines a concept in the standard way and automatically makes it a member of the provided category concept (i.e. adds a member slot to the provided category concept). Note that the category should already exist in the system.
* ''Concepts defineCategory: x displayName: y'' : This message to the Concepts namespace introduces a new category with name x (e.g., ''#{MyCategory}'') and display name y (note: displayName is optional). It introduces a concept for the name you provide and links it to Concept.Category with a ''superconcept'' slot. In addition the new category concept is added as a member of the ''Concepts.RootCategory'' category.
* Removing a category can be done by using the standard ''remove'' message for concepts. An automatic check is performed to see if the category/concept is referred to.
To illustrate the use of categories:
Concepts defineCategory: #{RegionsOfTheWorld}
displayName: 'Regions of the World Category'.
--> Creates the RegionsOfTheWorld concept as a
subconcept of Category, and adds a member slot to
RootCategory which refers to RegionsOfTheWorld.
(Concepts
defineConcept: #{SouthAmerica}
displayName: 'South America'
superconcept: Concepts.GeographicalRegion
category: Concepts.RegionsOfTheWorld).
--> creates the SouthAmerica concept and puts it as
a member of RegionsOfTheWorld.
Concepts.RootCategory declaredMember
--> a Collection which includes RegionsOfTheWorld.
Concepts.RegionsOfTheWorld
declaredMember: Concepts.Belgium
--> adds Belgium as a member of the RegionsOfTheWorld
category.
Concepts.RegionsOfTheWorld
computedMember:
'[Concepts.GeographicalRegion subconcept]'
--> Every subconcept of GeographicalRegion will
automatically become a computed member of the
RegionsOfTheWorld category.
Concepts.RegionsOfTheWorld members
--> Returns both the declared and computed members of
the RegionsOfTheWorld category.
Concepts.RegionsOfTheWorld declaredMember
do: [:each | each removeSilently].
--> removes all concepts that are declared members
of the RegionsOfTheWorld category.
! WARNING ! This removes the actual concepts,
don't use this code if you simply want to
remove the category membership of a concept.
Concepts.RegionsOfTheWorld remove
--> removes the category and its reference in the
RootCategory.
To browse the categories in the Concepts namespace you can use the ConceptBrowser of CoBro. You can open this browser via the Tools menu of the CoBro launcher, or by executing the following statement:
Concepts inspect
This opens up the following window which allows you to browse the concepts based on their position in the superconcept chain.
{{ cobro:cobro:screenshots:conceptbrowser2.jpg?430 |}}
If you right-click on the tree view you can switch between the superconcept tree or the category tree.
{{ cobro:cobro:screenshots:conceptbrowser.jpg?430 |}}
===== Concept Manipulation =====
Similarly to manipulating objects in Smalltalk, concepts are manipulated through message sends. Hence accessing a slot of the definition frame of a concept is done by sending a message. For example accessing the display name of the ''GeographicalRegion'' concept is done as follows:
Concepts.GeographicalRegion displayName
Note that a concept is defined by relating it to other concepts or terminal values. So the ''displayName:'' message we used earlier is actually a relationship concept.
The name of the message corresponds to the fully qualified reference of the relation that is used in the slot. In Smalltalk a message name may contain letters, numbers, and underscores. It may not begin with a number. Therefore the same restrictions apply to the fully qualified references of relations.
We follow the same naming convention as Smalltalk, hence the references to concepts begin with an uppercase letter, and the references to relations (messages) begin with a lowercase letter.
If the slot you try to access does not exist and it cannot be resolved through the concept parent chain then an exception is raised. The extended message lookup mechanism that takes care of this process is explained in a later section.
//Unary messages// in Smalltalk are messages with no arguments (e.g., ''myList printString''). These are used in CoBro-CML to access the values of a slot.
//Keyword messages// are messages with arguments (e.g., ''myList add: 'Hello'''). These are used to add a slot to a definition frame.
The message names in CoBro-CML correspond to the fully qualified reference of the relation. The relation reference can be used readily as a unary message to access slot values. If the reference is appended with a colon then it can be used as a keyword message to set the value for a slot. For example adding a comment slot:
Concepts.GeographicalRegion
comment: 'I just added a comment slot'.
Since we use the same message sending mechanism as Smalltalk, it is also possible to cascade messages. This is used to send two or more messages to the same concept, and is done by separating the messages with a semicolon. This way it is possible to avoid repeating the concept name for each message. For example adding extra slots to a concept when defining it can be done as follows:
(Concepts
defineConcept: #{GeographicalRegion}
displayName: 'Geographical Region'
superconcept: Concepts.Concepts) ;
comment: 'Adding an extra slot by cascading'.
Relations themselves are concepts. So before a relation can be used, a corresponding concept should be created.
We will first summarise the prerequisite slots for a concept, after which we extend them with prerequisite slots for a relationship concept.
The default //required// slots for a __concept__ are:
* Exactly one **conceptName** slot: this is used to uniquely identify a concept.
* exactly one **displayName** slot: this is used for printing the concept.
* one or more **superconcept** slots: this is used to connect a concept to a superconcept in order to set up a parent chain.
For a __relation__ these requirements are extended with:
* exactly one **multiplicity** slot: this is used to specify the multiplicity of the relationship (e.g.,one-to-one is ''#(1 1)'' and one-to-many is''#(1 #n)'').
* exactly one **destinationType** slot: this is used to specify the allowed destination of the relationship (e.g., ''Concepts.GeographicalRegion'').
Moreover a relationship should have ''Concepts.Relationship'' in its parent chain. This indicates to the CoBro kernel that the current concept should be treated as a ''Relationship'' (and not e.g. as a normal concept). In addition it 'inherits' a number of default slots that are necessary for the functioning of the CoBro tools.
For instance the definition of the ''comment'' relation is:
(Concepts
defineConcept: #{comment}
displayName: 'comment'
superconcept: Concepts.CoreRelationship)
multiplicity: #(1 #n) ;
destinationType: Concepts.STString ;
comment: 'A brief note clarifying
the purpose of a concept.'.
Depending on the characteristics of the relation, sending a unary message will either return a single value or a collection of values. For example the multiplicity of ''displayName'' is one-to-one, and the multiplicity of ''comment'' is one-to-many. This is shown by the following examples:
Concepts.GeographicalRegion comment.
--> returns an ordered collection containing
the comment slots
Concepts.GeographicalRegion comment: 'Test adding a slot'.
--> adds an extra comment slot which is allowed
since the relationship is defined as
one-to-many
Concepts.GeographicalRegion displayName.
--> returns a single value
Concepts.GeographicalRegion displayName: 'Geo'
--> the multiplicity of displayName is
one-to-one, a slot already exists, so this
throws an exception