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 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 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:
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:
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 theCategory
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 themember
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 asuperconcept
slot. In addition the new category concept is added as a member of theConcepts.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.
If you right-click on the tree view you can switch between the superconcept tree or the category tree.
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