Vision Classes
The basic rules for class and instance creation are presented in the document Vision Fundamentals. This document presents a much more detailed description of class and instance creation and management.
Review
Classes provide a basic tool for organizing your information. Vision comes with many pre-defined classes including Number, TimeSeries, and Entity. All Instances of a class respond to the same set of messages and use the same implementations to do so. All instances of a class have the same number and types of properties and use the same message names to refer to them.
Any class can be thought of as a table. The instances of the class form the rows of the table. The messages of the class form the columns in the table. This tabular organization is similar to the relational database model. However, in most relational systems, the actual data values in the columns are restricted to a fixed set of simple data types. No such restriction exists in Vision. The values in the table can include instances of any class, including instances of other user-defined classes, as well as simple values such as numbers, strings, and dates.
Class Specialization provides a means for defining a new class that inherits common properties and operations from another class. A class that is a specialization of another class is called a Subclass. A class inherits all the messages of its Superclass. Each time a new instance of the subclass is created, a corresponding instance is automatically created in its superclass. Messages can be defined or redefined at a subclass. If a message is redefined by a subclass, instances of the subclass will use the new message.
New classes are always created as a subclass of an existing class. All classes therefore inherit messages from a sequence of classes. If a message is not defined by the class itself, this sequence begins with a class' super class and continues with its super class' superclass until the class named Object is encountered. The class Object has no superclass. All other classes are direct or indirect subclasses of the Object class. The class Object therefore defines the messages that are available to all objects in the database.
Default Values
Every Vision class has an instance which represents its Default Instance. When a new class is created, an initial instance representing this default instance is also created. The message defaultInstance is defined as a class constant when a new class is created and returns the default instance associated with the class. This instance is often used to store initial values for specific properties of the class. Note that Vision does note treat this instance differently from other instances in its class. All special behavior for default instances is defined by methods under your control.
For Entity subclasses, the default instance is stored in the appropriate naming dictionary with the name Default. The default instance for an entity class can be accessed using the expression:
Named xxx Defaultwhere xxx is the Entity class desired. In most cases the class name itself returns the default instance. For example, the expression:
Named Currency Default = Currencyreturns a TRUE value.
This instance in all ways behaves like other instances in the class. To determine if an object is the default instance in its class, the isDefault and isntDefault messages are defined. Both return boolean (i.e., TRUE/FALSE) values. For example:
Named Currency US isDefaultreturns FALSE
Named Currency US isntDefaultreturns TRUE
Named Currency Default isDefaultreturns TRUE and
Named Currency Default isntDefaultreturns FALSE.
Many messages are defined to return objects from other classes. For example, the message company at the class Security could return an instance of the Company class. For example:
Named Security IBM companyreturns the object representing the Company IBM. If companies respond to the message sales, then the expressions:
Named Security IBM company salesand
Named Company IBM salesare identical and both return IBM's sales value.
Cash is a security that is not affiliated with a particular company. The value for the company message for this security could be a simple NA value. In the case, the expression:
Named Security Cash company saleswould generate the error:
>>> Selector 'sales' Not Found <<<since the expression Named Security Cash company returns the value NA and NA does not recognize the sales message. Although you might not ever request the sales value for the cash security directly, you might indirectly need it in a list. For example:
securityList do: [ #--- other stuff company sales printNL ; ] ;If the value of company were NA for some securities, the selector not found message would appear in your output.
Rather than storing an NA as the value of company for the cash security, you could store the default instance using:
Named Security Cash :company <- Named Company Default ;Now the expression:
Named Security Cash company saleswill return the value NA and you will not see the Selector Not Found error message. Default initializations such as this one are normally done as part of the instance creation process.
Class Descriptors
The class Schema ClassDescriptor is used to maintain information about the classes in your Vision database. Each instance of Schema ClassDescriptor corresponds to a specific class. Messages defined for this class enable you to record information pertaining to class hierarchy, naming conventions, and descriptions. The classDescriptor message is defined for each class to return the instance of the Schema ClassDescriptor class used to store descriptive information about the class.
Creating Subclasses
You can create a new class from an existing class using the expression:
!NewClass <- Object createSubclass ;The variable NewClass is defined to be a subclass of the class Object. Instances of the class NewClass will respond to all messages defined at Object as well as any additional messages defined at NewClass. By convention, class names such as NewClass are capitalized, although this is not a requirement.
When you send the createSubclass message to an existing class, the following operations are performed:
- A new subclass of the recipient class is created.
- An initial instance is created in the new class. This is the default instance for the class.
- A new instance is also created in each superclass of the new class. These instances are automatically linked via the super message.
- The message initializeGlobalSubclassProperties is sent to the new class.
- The message initializeGlobalInstanceProperties is sent to the new (i.e., the default) instance.
- A new instance of the class Schema ClassDescriptor is created and the class constant classDescriptor is defined at the new class to point to this new class descriptor instance.
- The default instance of the new class is returned.
Notice that new class creation also creates the default instance for the class. In the above expression, the variable NewClass can be interchangeably thought of as the name of the class or as the default instance in this class.
The message initializeGlobalSubclassProperties is used to set values in the default instance and define class constants and methods for the class. It is defined at Object to perform the following operations:
- Flag the first instance as the default by setting the value of the defaultFlag property to TRUE.
- Set the value of the code property in the default instance to the string "Default" .
- Define the class constant defaultInstance to return this first instance.
- Define the method asSelf to return the actual instance in a class, stripped of any extensions.
The message initializeGlobalSubclassProperties is often redefined by subclasses that require additional processing steps. To redefine the message, the following approach is often appropriate:
NewClass defineMethod: [ | initializeGlobalSubclassProperties | ^super initializeGlobalSubclassProperties ; # run parent's version # ... additional initializations go here ] ;The first line of this method is used to run the super class' version of the method first, followed by any class-specific rules you wish to define.
The message initializeGlobalInstanceProperties is used to initialize all new instances of a class. Since class creation actually involves creating a first instance in the class, this message is executed for this instance. It is described in detail in the next section.
Two variations of the createSubclass message have been implemented to encapsulate frequently used specialization techniques. The expressions:
class createSubclass: string ;and
class createSubclass: string at: object ;where class is the class to specialize, string is the name of the class to create and object is the place to name the class are used to automatically name the class and define additional standard protocol. For example, the expression:
Object createSubclass: "Sample1"is defined to automatically perform the following operations:
!Sample1 <- Object createSubclass ; Sample1 define: 'whatAmI' toBe: "Sample1" ; Sample1 define: 'isSample1' toBe: TRUE ; Object define: 'isSample1' toBe: FALSE ;The expression:
Object createSubclass: "Sample2" at: Sample1is defined to automatically perform the following operations:
Sample1 define: 'Sample2' toBe: Object createSubclass ; Sample1 Sample2 define: 'whatAmI' toBe: "Sample2" ; Sample1 Sample2 define: 'isSample2' toBe: TRUE ; Object define: 'isSample2' toBe: FALSE ;Notice that access to the class Sample2 is only available via Sample1. This technique is useful for restricting the messages at ^global to those that really need to be shared.
There may be cases where you need to run a version of createSubclass that skips the initialization steps. The basicSpecialized message is available for these situations. This message is a primitive that does the minimal work needed to create a new class. It is used primarily by methods that need to create temporary classes.
Creating Instances
You can create a new instance of any class using the expression:
!NewInstance <- NewClass new ;The variable NewInstance is defined to be an instance in the class NewClass. This instance will respond to all messages defined at NewClass and any of its super classes.
When you send the new message to an existing class, the following operations are performed:
- A new instance is created in the supplied class and all its super classes.
- The message initializeGlobalInstanceProperties is executed for this new instance.
- The new instance is returned.
The message initializeGlobalInstanceProperties is used to initialize values associated with the new instance. It is defined at Object to perform the following operations:
- Set the value of the baseObject property in the new instance to point to the new instance.
- Set the creationDate property in the new instance to the current date.
This message is often redefined by subclasses that require additional processing steps. To redefine the message, the following approach is often appropriate:
NewClass defineMethod: [ | initializeGlobalInstanceProperties | ^super initializeGlobalInstanceProperties ; # run parent's version # ... additional initializations go here ] ;The first line of this method is used to run the super class' version of the method first, followed by any class-specific rules you wish to define.
There may be cases where you need to run a version of new that skips the initialization steps. The message basicNew is a primitive version available for these situations.
The new message is normally used to create new instances of standard built-in classes such as List and TimeSeries. The message createInstance: has been defined to provide a more comprehensive approach to instance creation, performing additional initializations based on information in its classDescriptor. The createInstance: message accepts a single keys value or list of values:
NewClass createInstance: keysand performs the following operations:
- Creates a new instance of the class using new.
- Runs the initializeKeys: message passing the supplied keys as a parameter. This message is used to assign the parameters provided to properties in the new instance. This message is defined at Object to assign the parameter passed to the code property. Many subclasses will want to redefine this method.
- Runs the initializeDefaults message. This message is used to initialize any properties that have default values. Any property that does not have an explicit default value will have the value NA.
- Runs the initializeLocalAttributes message. This message is used to initialize any additional properties required by the subclass. This message is defined to take no action at Object. Many subclasses will want to redefine this method to address local initialization issues.
- Runs the initializeNames message which runs the initializeNamesFor: stringList message for the class. If the class descriptor has an associated naming dictionary, the strings in stringList will be added to this dictionary to reference this new instance.
- Returns this new instance.
The createInstance message is defined to call createInstance: NA.
For example, the expression:
NewClass createInstance: "NewClass1"will automatically perform the following operation:
NewClass createInstance do: [ :code <- "NewClass1" ]
You could change the instance creation rules by modifying one or more of the initialization messages as appropriate. For example, assume you want to use two properties, cusip and ticker to initialize new Company instances and that you want to initialize the property name to the ticker's value:
Company defineFixedProperty: 'cusip' . defineFixedProperty: 'ticker' . defineFixedProperty: 'name' ; Company defineMethod: [ | initializeKeys: keys | :cusip <- keys at: 1 ; # first element in list :ticker <- keys at: 2 ; # second element in list :code <- cusip ; # for consistency ] ; Company defineMethod: [ | initializeLocalAttributes | ^super initializeLocalAttributes; # run version defined at Entity :name <- ticker ; ] ; Company defineMethod: [ | initializeNames | ^self initializeNamesFor: cusip, ticker ; ] ;The expression:
Company createInstance: "12345610", "Dummy"will create a new Company instance, initialize its cusip, ticker, and name properties, and update the naming dictionary with the two new names. The expressions:
Named Company \12345610and
Named Company Dummyboth return this newly created company instance.
Deleting Instances
Instances can be flagged for deletion without actually removing the object from the database. This is a recommended approach for most applications, giving you an opportunity to 'undelete' an accidental deletion if necessary. Several properties have been defined to aid in tracking deletion information. Undeleted objects have values of NA for all of these properties:
Message | Definition |
deletionFlag | TRUE if flagged for deletion |
deletionDate | Date object was flagged for deletion |
deletionReason | String explaining deletion reason |
Where object is the object to be flagged for deletion and string is the reason for deletion, the expression:
object flagForDeletionWithReason: stringis used to flag an object for deletion. It is defined to perform the following operations:
- Set the value of deletionFlag to TRUE.
- Set the value of deletionDate to current date.
- Set the value of deletionReason to supplied string.
- Run the object's implementation of the method cleanupDeletedObject.
The cleanupDeletedObject message executes the following steps:
- Run the object's version of cleanupLocalAttributes. By default, this message does not do anything. It should be redefined as appropriate for specific classes.
- Run the object's version of cleanupLocalNames. By default, this message deletes the object's code from the class' naming dictionary, if one exists. This message can be redefined or augmented at specific classes as needed.
Several messages have been defined to utilize the deletionFlag property:
Message | Definition |
isActive | TRUE if object's deletionFlag isNA |
isntActive | TRUE if object isActive != TRUE |
isDeleted | Same as isntActive |
isntDeleted | Same as isActive |
To actually delete objects, you use the delete or rdelete message. The delete just deletes the instance without considering the inheritance hierarchy. The rdelete version deletes the recipient object and all instances in its super hierarchy. There is no reason not to always use the rdelete version. Note that these messages should be used after the flagForDeletionWithReason: message has been run to cleanup other information related to the instance. Once an instance has been deleted, any references to the instance from other properties will return a Shadow instance for the class. The shadow instance will respond to all the messages in the class, but will have NA values for all its properties.
Basic Access
All objects respond to the message instanceList by returning an object containing the list of all instances in its class. The expression:
NewClass instanceListreturns the list of instances in the class NewClass. The instanceList count should equal 1 directly after a class is created. This instance is the default instance.
The isDefault and isntDefault messages can be used to identify objects that are (are not) the default instance in a class. For example, the expression:
NewClass instanceList select: [ isntDefault ]would be used to select all the non-default instances of the class NewClass.
Recall that the createInstance message creates a new instance in all the super classes of an object. In the example:
Object createSubclass: "Sample1" ; Sample1 createSubclass: "Sample1A" ; Sample1 createSubclass: "Sample1B" ; !s1a1 <- Sample1A createInstance: "Sample1A1" ; !s1a2 <- Sample1A createInstance: "Sample1A2" ;The class Sample1A would contain 3 instances (the default plus the two new instances), the class Sample1B would contain 1 instance (the default), and the class Sample1 would contain 5 instances (the default plus one instance for each instance of its subclasses).
The variable s1a1 returns an instance of the class Sample1A. It is sometimes necessary to access the actual instance in the object's superclass. The expression:
s1a1 superreturns an instance of the class Sample1. This instance will respond to all the messages defined at Sample1 and its super classes, but will not know about any messages defined at the class Sample1A. You can navigate back to the "true" object using the expression:
s1a1 super asBaseObjectThe asBaseObject message will return the actual object that was created with the createInstance message. The expression:
s1a1 super asBaseObject = s1a1returns the value TRUE. The asBaseObject message is useful for assembling all the objects belonging to any of the subclasses of a class. For example, the expression:
Sample1 instanceList send: [ asBaseObject ]could be used to return the list of all actual objects that are defined at Sample1 or any of its subclasses. The simple instanceList message would return a list of 5 objects all of class Sample1, whereas the above expression would return a list of 5 objects with 3 objects of class Sample1A, 1 object of class Sample1B, and 1 object of class Sample1.
The masterList message has been defined to return the non-default, base object instances associated with a class. For example, the expression:
Sample1 masterListis the same as:
Sample1 instanceList select: [ isntDefault ] . send: [ asBaseObject ]The returned list in both cases would contain 2 instances (s1a1 and s1a2). The message activeList is used to return all instances of a class (as base objects) that are active (not flagged for deletion) and not default.
The message asSelf is similar in function to the asBaseObject message and often returns the same object. It is used to strip any extensions from an object. For example, the expression:
s1a2 = (s1a2 extendBy: [ !x ] )would return FALSE. The expressions:
s1a2 = (s1a2 extendBy: [ !x ] . asSelf )and
s1a2 = (s1a2 extendBy: [ !x ] . asBaseObject)would both return TRUE. By convention, the asBaseObject message is used to navigate from a super class to a subclass and the asSelf message is used to strip extensions from an object.
The message showInheritance can be sent to an object to see the classes in its hierarchy. For example, the expression:
Integer showInheritancedisplays the following:
*** Inheritance Map For Class: Integer *** Object | Ordinal | Number | Integer <===== YOU ARE HEREThe expression:
Number showInheritancedisplays:
*** Inheritance Map For Class: Number *** Object | Ordinal | Number <===== YOU ARE HERE Double Float Integer
The expression:
classA inheritsFrom: classBreturns TRUE if classA is a subclass of classB. The expression:
classA isSuperClassOf: classBreturns TRUE if classA is a super class of classB. For example:
Integer inheritsFrom: Numberreturns TRUE and:
Integer isSuperClassOf: Numberreturns FALSE.
Clustering Issues
When Vision creates a new instance of a class, it needs to put it somewhere. Deciding where it goes is an object clustering issue.
Vision views classes as tables and instances as rows in those tables. This is more than a conceptual convenience -- it is also the basis for Vision's object clustering model. Every Vision object is divided into layers. Each layer corresponds to a class in the object's superclass hierarchy and lives in a table associated with that class. As a result, when you reference an object, you are also referencing the collection of tables that contain that object. Each of those tables is known as a Cluster or Store. The set of tables used to contain all the layers of an object is known as a Cluster Chain.
In Vision, you reference a class indirectly through its instances. The createSubclass message, for example, always returns the default instance of the class it creates. When you create a new instance of a class, you are actually asking an existing instance to create another instance for you. Normally, the new instance will have a row created in each level of the original instance's cluster chain.
By default, when you send the new message or one of the createInstance messages to an instance of a non-Collection, non-Built-in class, you are asking Vision to create a new instance in the same cluster chain as the existing instance. That happens because all of these messages ultimately send a clusterNew message to the original instance. The clusterNew message is a primitive message that requests the creation of a new instance in the same cluster chain as its recipient.
Not all of the instances of a class need to reside in the same cluster chain. In many cases it would be inconvenient or inefficient if they did. For example, when Vision creates objects for its own use, it creates new clusters to hold them. The Built-in class String and Collection classes automatically create new clusters to store new strings and collections as they are created. References to these new clusters are typically temporary variables or properties if the clusters are to be permanently stored.
You too can create new clusters. The newPrototype message creates a new cluster chain capable of holding instances of the same class as its recipient. All objects respond to the newPrototype message. The newPrototype message creates cluster chains that are initially empty; for example:
!newCluster <- MyClass newPrototype ; newCluster instanceList count printNL;returns the value 0.
Because there are no instances in these clusters, the newPrototype message returns a shadow instance of the class. The shadow instance cannot be used to store values. To get an instance you can use, you must send a message like new, clusterNew, or one of the createInstance messages to the shadow instance returned by newPrototype.
Classes whose instances are divided across multiple cluster chains are said to be Horizontally Partitioned. There are reasons to horizontally partition a class and tradeoffs associated with those reasons. By default, the instance creation messages inherited from Object use the clusterNew message to keep most classes highly clustered. This strategy assumes that you will tend to operate on collections of these instances. In contrast, the instance creation messages inherited from Collection use the newPrototype new message sequence to horizontally partition the collection classes. This strategy assumes that unrelated collections will not be used together and will probably have different lifetimes.
The primary reasons to horizontally partition a class are:
- to improve instance creation and update performance
- to store instances of the same class in different object spaces
- to segregate unrelated instances of the same class
- to segregate instances with different lifetimes
As the number of instances in a cluster grows, the cost of creating and updating instances in that cluster increases. That is because Vision attempts to keep the instances in a cluster near each other on disk. Keeping those instances near each other improves the performance of operations like select:, groupedBy:, and do:. As the size of the cluster grows, however, Vision needs to do more work to maintain instance proximity. Eventually the cost of maintaining proximity outweighs its benefits and the class should be horizontally partitioned.
A cluster resides in just one object space. Since a cluster can only reside in a single object space, a class must be horizontally partitioned if its instances are to be stored in multiple object spaces. The most common reason for doing that is to save your own personal instances of a class whose primary cluster you cannot save.
The primary benefit to a high degree of clustering is query performance. Clustering is of no benefit and not worth the cost for instances that are not used that way. For example, two lists used to hold totally different types of elements are unlikely to be used together in a query. Even though they are both lists, there is no benefit to keeping them in the same cluster.
Clusters disappear from the database when they are no longer needed. They are no longer needed as soon as there are no references to any of their objects. If the same class is used to create instances that have different lifetimes, it makes sense to horizontally partition the class. Vision uses this strategy when it creates objects that represent the intermediate results of a program. The same strategy can be employed in your applications.
In summary:
- The new and clusterNew messages are identical for most classes. By default, most classes have one cluster and new instances are created in this cluster.
- The newPrototype message is used to start a new cluster for a class. This cluster initially has 0 instances.
- The Collection subclasses are not clustered by default. The new message creates the new instance in a new cluster. The clusterNew message creates the new instance in the same cluster as the recipient collection.
- Strings are not clustered by default. As new strings are created, they are stored in separate clusters. Modifying a string will return a new string but it will not be in the same cluster as the original string. Additional techniques are used to recluster strings if needed.
When you send the newPrototype message to an object, it starts a new cluster for physically storing instances. The new structure is at the same level of inheritance as the original. Messages defined at the original class or any subsequent clusters are seen by all clusters. The instanceList message only returns the list of instances in the same physical cluster as the recipient.
By convention, you should always create a new initial instance in a new cluster and set its default flag much the same way the initial instance is created when a new class is defined. It is also useful to create a class constant that tracks the different clusters that have been created. For example:
#-- Create a list to store the first instance of each cluster Currency define: 'storeXRef' toBe: List new ; #-- Add the initial cluster as the first entry Currency storeXRef , Currency ; #-- Define a method to create a new cluster store Currency defineMethod: [ | createNewStore | #-- create new store and first instance in store !store <- ^self newPrototype new do: [ :defaultFlag <- TRUE ; #- other initializations ] ; #-- use this if you want to name the store at ^global ^global define: whatAmI toBe: store ; #-- add new store to storeXRef list ^self storeXRef, store ; store ] ; #-- define fullInstanceList to return instances across clusters Currency defineMethod: [ | fullInstanceList | storeXRef collectListElementsFrom: [ ^self instanceList ] ] ;More information about clustering Strings, Lists and IndexedLists is available.