2. Traits

Previous chapter

Next chapter

Traits provide a way to share sets of behavior (instance and class methods) between classes, other than using inheritance from a shared superclass. This provides a result similar to multiple inheritance.

Note that traits area new feature in v3.7.2 and should be considered preview. There are known conditions that are not handled optimally, such as creating multiple traits with the same name. Current versions of GemBuilder for Smalltalk (GBS) do not provide support for Traits.

It is not recommended to use traits when doing development in an environment that is not traits-aware, such as GBS 8.8.1 and earlier versions. Methods that originated from trait methods appear to be normal methods in existing tools, and in a non-trait-aware version, it may be difficult to avoid inadvertent and difficult-to-detect changes.

Pharo-style traits in GemStone

GemStone's traits are similar to Pharo traits. GemStone does not support trait composition; only one instance-side trait and one class-side trait can be added to a class. GemStone traits are stateful, and support class and class instance variables access as well as instance variable access.

Terminology

In this discussion, "trait" is used to refer to the logical trait, the set of instance and class methods that are added to a class (although the instance and class sides are added independently). "Trait" is the class that provides the main entry point for a trait, and which implements the instance side trait methods; it provides access to the class side trait methods, but these are associated with a separate class, the ClassTrait.

The term "trait method" refers to the method defined on the trait, which is distinct from the compiled method on the class that originated from the trait method’s source string.

Creating and Using traits

A trait is created similar to how a class is created, and methods are added to the trait in a way similar to creating a method on a class. The trait is then added to each of the target classes that will use the trait's behavior.

When a trait method is defined, it is compiled for validation, but the trait methods are stored as source strings. When you add the trait to a target class, these source strings are compiled into the method dictionary of the target class.

When you add, modify, or remove a method on the trait, the methods that originated from trait methods are automatically updated on the classes that are using that trait.

The trait's instance, class, and class instance variables must match or be a subset of the instance, class, and class instance variables of each class to which the trait will be added.

Only variables that are accessed from methods on the trait are required to be defined on the trait.

Added Classes

The following classes have been added to support traits:

AbstractTrait

This is an abstract superclass of Trait and ClassTrait

Trait

Trait represents the instance side methods for a trait. An instance of Trait knows its corresponding class side trait (ClassTrait), which is available by sending #classTrait to the instance.

ClassTrait

ClassTrait represents the class side methods for a trait.

GsTraitImpl

The Trait and ClassTrait instances for a given trait reference a shared instance of GsTraitImpl, which contain the source strings, categories, the classes that are using this trait, and other implementation information. This class is not intended to be used directly.

Precedence

If a selector that is part of a trait is already implemented directly by the target class, the trait's implementation is not used. If a method with the given selector is later compiled directly on the class, it will override an existing trait implementation.

Example

This is an example of a minimal set of trait creation and use, assuming an existing class MyClass:

Trait
    name: 'TestTrait' 
    instVars: #(  ) 
    classVars: #(  ) 
    classInstVars: #(  ) 
    inDictionary: UserGlobals.
 
TestTrait compile: 'logFileName
 ^ self class logFileName' category: 'TestTrait-Filename'.
  
TestTrait classTrait compile: 'logFileName
 ^ ''/tmp/traitLogFile.out''' category: 'TestTrait-Filename'.
  
MyClass addInstanceAndClassTrait: TestTrait.
 
exec MyClass logFileName %
exec MyClass new logFileName %

Image support for traits

The GemStone image transparently supports traits. Note that tools such as GBS may not distinguish methods that originate from trait methods from ordinary methods or support filein or other trait operations.

Fileout support for traits

Image methods in Behavior and ClassOrganizer transparently handle fileout of methods and traits. The method source for methods that originate from trait methods is not filed out as if they were methods on the class; instead, expressions that can recreate the traits are included.

For example:

! ------------------- Traits for MyClass
doit
MyClass addTrait: TestTrait.
MyClass addClassTrait: TestTrait classTrait.
true.
%

You must separately ensure that the trait (in the example, a trait named TestTrait) is filed out. This can be done using new methods in Class, Behavior, and ClassOrganizer:

Behavior >> fileOutTraits
Returns a String or MultiByteString with all the receiver's trait specifications (traits and classTraits) in Topaz Filein format. Result should be filed in after filing in methods.

Behavior >> fileOutTraitsOn: stream
Writes code to create trait registrations onto the given stream in filein format.

ClassOrganizer >> fileOutTraitsClassesAndMethodsInDictionary: aSymbolDictionary on: aStream
Files out all source code for classes in aSymbolDictionary in Topaz filein format on aStream.

ClassOrganizer >> determineTraitFileoutOrder: traitdict
Returns an ordered collection of the values that are traits in traitdict, specifying the order of fileout. The argument should be a SymbolDictionary.

ClassOrganizer >> fileOutTraitDefinitions: order on: stream
Writes out code in topaz filein format on the given stream, that creates the given traits. order arg is an array of traits.

ClassOrganizer >> fileOutTraits: classdict order: order on: stream
File out each class's trait registrations.

GsFileIn support for traits

GsFileIn includes support for the following topaz commands that support traits. For more detail on these topaz functions, see Added functions for trait support.

TRCLASSMETHOD

TRCLASSMETHOD:

TRMETHOD

TRMETHOD:

TRREMOVEALLMETHODS

TRREMOVEALLCLASSMETHODS

ClassOrganizer support for traits

In addition to the fileout support methods above, the following methods have also been added to find out information about traits.

ClassOrganizer >> traitImplementorsOfReport: aSelector
Returns a String describing the methods that are implementors of the specified selector in traits.

ClassOrganizer >> traits
Returns the list of Traits found by the receiver during the class scan.

ClassOrganizer >> traitStringsReport: aString ignoreCase: icBool includeTraitComments: commentsBool
Return a report of the trait methods that match the given string.

Information on Compiled Methods from Traits

The following methods are available to get information about a compiled method, allowing you to distinguish a regular methods from a trait or Rowan method:

GsNMethod >> isFromTrait
Return true of the method originated from a trait method definition

GsNMethod >> origin
If the receiver is a trait method, answer the Trait instance that created the method.
If the method is a loaded method, answer the Rowan loaded method instance.
Otherwise answer the class of the method.

Distinguishing Traits

The following methods distinguish a trait from another object:

Object >> isTrait
AbstractTrait >> isTrait

Trait management in Class

The following methods allow you to add, remove, and get information about traits.

Class >> addClassTrait: aClassSideTrait 
Class >> addInstanceAndClassTrait: anInstanceSideTrait
Class >> addTrait: anInstanceSideTrait 
Class >> classTraits 
Class >> classTraits: stringExpressionDefiningClassTrait
Class >> removeClassTrait: aClassSideTrait 
Class >> removeTrait: anInstanceSideTrait 
Class >> traits 
Class >> traits: stringExpressionDefiningTrait

Instance and class traits are managed separately; methods without 'class' require or return an instance side trait.

It is not required that the instance and class traits be related to each other. The exception is addInstanceAndClassTrait:, which is a convenience method; this adds both the instance and the class trait to the receiver (the class trait is derived from aTrait classTrait).

Topaz support for traits

Added functions for trait support

SET TRAIT: aTrait

Sets the current trait. After you issue SET TRAIT, you can list the trait's categories and methods with the TRLIST CATEGORIES command. You can select a category to work with through either the SET CATEGORY: or the CATEGORY: command.

The current trait may also be redefined by the TRLIST CATEGORIESIN:, TRMETHOD:, TRCLASSMETHOD:, and TRFILEOUT TRAIT: commands. The current trait is cleared by the ABORT, LOGOUT, LOGIN, and SET SESSION commands.

TRSTRINGSIC anObjSpec

Reports Trait methods that contain the argument in their source string, and trait comments that include the argument. The comparison is case-insensitive.

Expects one argument, an objectSpecification, the value of which may be a String or Symbol. The search is in the compilation environment 0 (see ENV).

Equivalent to:

ClassOrganizer new traitStringsReport: (aString) ignoreCase: true includeClassComments: true

TRSTRINGS anObjSpec

Reports Trait methods that contain the argument in their source string, and trait comments that include the argument. The comparison is case-sensitive.

Expects one argument, an objectSpecification, the value of which may be a String or Symbol. The search is in the compilation environment 0 (see ENV).

Equivalent to:

ClassOrganizer new traitStringsReport: (aString) ignoreCase: false includeTraitComments: true

TRREMOVEALLMETHODS [ aTrait ]

Removes all methods from the trait whose name you give as a parameter. The specified trait will automatically become the "current" trait.

Example: trremoveallmethods TArray

TRREMOVEALLCLASSMETHODS [ aTrait ]

Removes all class methods in from the trait whose name you give as a parameter. The specified trait will automatically become the "current" trait.

Example: trremoveallclassmethods TArray

TRMETHOD[: aTrait]

Compiles an instance method for the trait whose name you give as a parameter. The trait of the new method will automatically become the "current" trait. If you don't supply a trait name, the new method is compiled for the current trait (as set with the SET TRAIT:, TRFILEOUT TRAIT:, TRMETHOD:, or TRLIST CATEGORIESIN: command). Compiles in the compilation environment 0.

Text of the method should follow the TRMETHOD: command on subsequent lines. The method text is terminated by the first line that starts with the '%' character. For example:

After the method has been successfully compiled in the trait, the method will be compiled in the dependent classes registered for the instance side trait.

TRLOOKUP

TRLOOKUP includes subcommands that allow you to browse traits and trait methods.

TRLOOKUP method selectorSpec

The arguments after 'TRLOOKUP' are case sensitive. Using the current trait, search for specified instance method. Print the trait in which method found, and list the category and source code. See <selectorSpec> in Object_Specification_Formats.

TRLOOKUP cmeth selectorSpec
TRLOOKUP classmethod selectorSpec

The arguments after 'TRLOOKUP' are case sensitive. Using the current trait, search for specified class method. Print the trait in which method found, and list the category and source code. See <selectorSpec> in Object_Specification_Formats.

TRLOOKUP aTrait >> selectorSpec [envIdInteger]

Make the specified trait the current trait, and do a lookup for the specified instance method A <traitname> may not be one of 'meth', 'method', 'cmeth', 'classmethod'

TRLOOKUP aTrait class >> selectorSpec [envIdInteger]

Make the specified trait the current trait, and do a lookup for the specified class side method. A <traitname> may not be one of 'meth', 'method', 'cmeth', 'classmethod'

TRLIST

TRLIST includes subcommands that allow you to browse traits and trait methods.

TRLIST TRAITS

Lists all of the dictionaries in your symbol list and all of the traits they contain.

Takes an optional argument aString, if given, limit the result to those traits for which (aTrait name includesString: aString)==true.

TRLIST TRAITSIN: aSymbolDictionary

Lists the traits in a specific dictionary. For example: trlist traitsin: UserGlobals

TRLIST CATEGORIESIN[: aTrait ]

Lists all of the instance and class method selectors for the named trait, by category, in the compilation environment 0 (see ENV). If you do not specify a trait name, the categories of the current trait will be listed. If you do specify a trait name, that trait becomes the current trait for subsequent Topaz commands. (The current trait is also set with the SET TRAIT:, FILEOUT TRAIT:, or TRMETHOD: command.)

TRLIST CCATEGORIES[: aTrait ]

Lists all of the class method selectors for the named trait, by category, in the compilation environment 0 (see ENV). If you do not specify a trait name, the class categories of the current trait will be listed. If you do specify a class name, that class becomes the current class for subsequent Topaz commands.

TRLIST CLASSMETHOD[: anObjectSpec ]
TRLIST CMETHOD[: anObjectSpec ]

With an argument that is a <selectorSpec>, lists the category and source of the given classmethod selector for the current trait in the compilation environment 0 (see ENV). (The current trait is set with the SET TRAIT:, TRFILEOUT TRAIT:, TRMETHOD:, or TRLIST CATEGORIESIN: command). See <selectorSpec> in Object_Specification_Formats

TRLIST CSELECTORS [ aString ]

List selectors of all class methods for the current trait in compilation environment 0 (see ENV). Accepts an optional string token, for example: LIST CSEL policy lists only selectors which contain 'policy' using case-insensitive match.

TRLIST CPRIMITIVES [ aString ]

List selectors of all class methods for the current trait in compilation environment 0 which are primitives. Accepts an optional string token, for example: TRLIST CPRIM at lists only selectors which contain 'at' using case-insensitive match.

TRLIST ICATEGORIES[: aTrait ]

Lists all of the instance method selectors for the named trait, by category, in the compilation environment 0. If you do not specify a trait name, the categories of the current trait will be listed. If you do specify a trait name, that trait becomes the current trait for subsequent Topaz commands.

TRLIST LINENUMBERS

If a current trait method is set, lists this method with line numbers prefixed to the source lines. The current trait method can be set using TRLOOKUP.

TRLIST METHOD: aSelector

Lists the category and source of the given instance method selector in the current trait and compilation environment 0.

TRLIST PRIMITIVES [ aString ]

List selectors of all instance methods that are primitives, in the current trait and compilation environment 0. Accepts an optional string token; this lists only selectors for primitive methods that contain <aString>, using case-insensitive match.

TRLIST SELECTORS [ aString ]

List selectors of all instance methods in the current trait and compilation environment 0. Accepts an optional string token; this lists only selectors that contain <aString>, using case-insensitive match.

TRIMPLEMENTORS aSelectorSpec

Expects one argument, an selectorSpec, the value of which is either a String or Symbol.

This command is equivalent to:(ClassOrganizer newForEnvironment: <current env>) traitImplementorsOfReport: aString

TRFILEOUT

Writes trait definitions and/or methods to a named file in a format that can be fed back into Topaz with the INPUT command. The current compilation environment(see ENV) is ignored by traits and environment 0 is exclusively used. If you supply a parameter that does not match one of the FILEOUT sub-commands, Topaz will assume it is a method selector for the selected trait and will attempt to file it out. The first line of the output file is a fileformat command reflecting the way output was generated.

Examples:

trfileout trait: TReadStreamLegacy tofile: tlegacy.gs format: 8bit

set trait TReadStreamLegacy
trfileout category: 'Instance Creation' tofile: tlegacy.gs format: utf8

Keyword arguments must be provided in this order:

exactly one of TRAIT: CATEGORY: CLASSCATEGORY: METHOD: CLASSMETHOD:
optional TOFILE:

If TOFILE: is given, then it may be followed by an optional FORMAT:

If FORMAT: is omitted, output is is controlled by the last FILEFORMAT.

TRFILEOUT TOFILE: filename

The output of the FILEOUT command is sent to the file that you name following the TOFILE: keyword. For example:

trfileout trait: TReadStreamLegacy tofile: tlegacy.gs

If you specify a host environment name such as $HOME/foo.bar in UNIX as the output file, Topaz will expand that name to the full filename.

If the output file does not contain an explicit path specification, Topaz writes to the named file in the directory in which you started Topaz.

If the filename begins with '&', the '&' is discarded and output is appended to the specified file, otherwise the specified file is overwritten.

TRFILEOUT FORMAT: format

Overrides the current FILEFORMAT setting. Argument must be a string, either UTF8 or 8BIT. The FORMAT: keyword must be after a TOFILE: keyword.

TRFILEOUT TRAIT: aTrait

Writes out the trait definition and all the method categories and their methods. The trait that you file out will be selected as the current trait for use with other Topaz commands.

TRFILEOUT CATEGORY: aCategoryName

Writes out all the methods contained in the named category for the current trait. For example

set trait TReadStreamLegacy
trfileout category: 'Accessing' tofile: tlegacy.gs format: utf8

TRFILEOUT CLASSCATEGORY: aCategoryName

Writes out all the class methods contained in the named category for the current trait. For example:

set trait TReadStreamLegacy
trfileout classcategory: 'Instance Creation' tofile: tlegacy.gs format: utf8

TRFILEOUT CLASSMETHOD: selectorSpec

Writes out the specified class method (as defined for the current class). The category of that method is automatically selected as the current category for use with other Topaz commands. Method lookup uses current compilation environment (see ENV). For example:

set trait TReadStreamLegacy
trfileout classmethod: new

TRFILEOUT METHOD: selectorSpec

Writes out the specified method (as defined for the current class). The category of that method is automatically selected as the current category for use with other Topaz commands. The method lookup uses current compilation environment (see ENV). For example:

set trait TReadStreamLegacy
trfileout method: next

You may omit the "METHOD:" keyword if the method selector does not conflict with any of FILEOUT's subcommands. For example, to file out a method named "category:", you'd need to explicitly include the METHOD: keyword as shown here:

trfileout method: category:

TRCLASSMETHOD[: aTrait ]

Compiles a class method for the trait whose name you give as a parameter. The trait of the new method will automatically become the current trait. If you don't supply a trait name, the new method is compiled for the current trait (as set with the SET TRAIT:, TRFILEOUT TRAIT:, TRMETHOD:, or TRLIST CATEGORIESIN: command).

Text of the method should follow the TRCLASSMETHOD: command on subsequent lines. The method text is terminated by the first line that starts with the '%' character.

After the method has been successfully compiled in the trait, the method will be compiled in the dependent classes registered for the class side trait.

 

Previous chapter

Next chapter