Vision FAQ: Frequently Asked Questions
Syntax Problems
- Method Does Not Print
- Printing Wrong Value In Calculation
- Selector Not Found message for select: do:
Message Usage
- Multiple Sorts
- Displaying messages for a Collection Element
- Grouping List into a range of values
- Grouping elements by string values
- Sending showMessages message to a list
Techniques
I defined a method to compute the price-earnings ratio and it does not print anything. What did I do wrong?
The most likely problem is that you terminated your method with the ; character so it is not returning anything. For example, the method:
Company defineMethod: [ | peRatio | price / earningsPerShare ; ]would perform the calculation but not return the value. Remove the terminating ; character and the result of the calculation will be returned.
See: The noValue Value
I am printing the result of a ratio calculation and the value printed is the value of the last item in the calculation. What happened to the calculation?
This is probably a precedence order problem. For example, the program:
myList do: [ price / earningsPerShare printNL ; ] ;will print the value of earningsPerShare. Because unary messages are evaluated before binary messages, the value is printed prior to performing the calculation. The results of the calculation are lost in this case. You could use either one of the following programs to print the correct value:
myList do: [ (price / earningsPerShare) printNL ; ] ;or
myList do: [ price / earningsPerShare printNL: 10 ; ] ;
See: Precedence Order
I am getting the Selector Not Found message for select: do:. What does this mean?
You are most likely missing a . character after the select: block. The program:
myList select: [ sales > 25000 ] # <-- . omitted do: [ ticker printNL ] ;would produce this error message. The correct expression is:
myList select: [ sales > 25000 ] . do: [ticker printNL];The program:
"Automotive" take: 4. concat: " Industry" # ^ this dot needs to be preceded by a spaceillustrates a common variation of this problem. In this case the . character following the number 4 is interpretted as part of the number so the Selector Not Found message indicates that take:concat: is not defined. The correct form of this expression is:
"Automotive" take: 4 . concat: " Industry"
See: The dot operator, The . and Collections
I want to sort my list from highest to lowest score and then alphabetically for ties. How do I do this?
The sortUpBy:then: and sortDownBy:then: messages do not work in this case because you want one of your sorts to be ascending and one descending. To accomplish this, you need to take advantage of the fact that the sort messages produce "stable" sorts. This means that when a sort message is applied to a list, the original order of the list is preserved if the sort produces a tied value. If you apply several sort criteria in reverse order (i.e., the most detailed level of sort first), you can produce the desired results. For example, the expression:
myList sortUp: [ name ] . sortDown: [ score ]first sorts myList alphabetically by name. This list is then sorted from highest to lowest score. Any score values that are ties preserve the order of the alphabetical list. As a result, the list is sorted in the desired order.
See: Sorting Collections
How do I group my list into ranges of values?
The message groupedBy: usingCutoffs: was designed for this purpose. The first parameter is the block used to evaluate the list elements; the second parameter is a list of cutoff points to be used to indicate the range boundaries. For example, to group the company master list into price-earnings ratio ranges of < 0, 0-5, 5-10, 10-20, and > 20 you would use:
Company masterList groupedBy: [ price / earningsPerShare ] usingCutoffs: 0, 5, 10, 20This expression returns a list whose elements correspond to the five buckets created (one for each range). Each element of this returned list is extended by the variables lowerBoundary, upperBoundary, categoryNumber, and groupList. The message lowerBoundary returns the lower limit of the range (or NA if it is the first element). The message upperBoundary returns the upper limit of the range (or NA if it is the last element). The message categoryNumber identifies the group (in this case a number from 1 to 5). The groupList message returns the list of original elements falling in this group.
The following program performs the grouping and displays the results in a useful format:
Company masterList groupedBy: [ price / earningsPerShare ] usingCutoffs: 0, 5, 10, 20 . do: [ lowerBoundary isntNA ifTrue: [ upperBoundary isntNA ifTrue: [ lowerBoundary print: 3 ; "-" print ; upperBoundary print: -3 ; ] ifFalse: [ " > " print ; lowerBoundary print: -4; ]; ] ifFalse: [ " < " print ; upperBoundary print: -4 ; ] ; groupList count printNL ; ] ;
I am trying to find all the elements in a groupList that are in the current sector and the list is always empty even though I know there are matches. Why?
Your program probably looks something like this:
myList groupedBy: [ industry sector ] . do: [ !currentSector <- ^self ; currentSector code print: 10 ; groupList count print ; groupList select: [ industry sector = ^my currentSector ] . count printNL ; ] ;Since all the elements in groupList should be in the currentSector by definition, you would expect the two counts to be the same. In actuality, the second count is 0. The selection criteria compares the value of industry sector to the object ^my currentSector. Because currentSector was created as the result of a groupedBy: operation, it is really an extension of a Sector object. As a result, the two objects are not equal.
The following programs offer two alternatives that will both produce correct results:
myList groupedBy: [ industry sector ] . do: [ !currentSector <- ^self ; currentSector code print: 10 ; groupList count print ; groupList select: [ industry sector code = ^my currentSector code ] . count printNL ; ] ;or
myList groupedBy: [ industry sector ] . do: [ !currentSector <- ^self ; currentSector code print: 10 ; groupList count print ; groupList select: [ industry sector = ^my currentSector asSelf ] . count printNL ; ] ;The asSelf message strips any extensions off the object and returns the "pure" sector object.
See: Instance Hierarchy, Object Extensions
I created a property 'rating' for company and assigned the values A, B, or C to some instances. When I group my company list by rating, there are several groups with the same rating. Why?
The grouping operation groups objects into unique buckets. If you assigned your ratings using two distinct string objects that both contained the contents A, they would group into distinct buckets. This would happen if you assign the values in different executions. For example, if you execute the expression:
Named Company GM :rating <- "A" ;and then execute the expression:
Named Company IBM :rating <- "A" ;in a separate execution, the ratings will not be grouped together. To perform this assignment correctly, you could create class constants that represent each rating as illustrated below:
Company define: 'ratingA' toBe: "A" ; Company define: 'ratingB' toBe: "B" ; Named Company GM do: [ :rating <- ratingA ] ; Named Company IBM do: [ :rating <- ratingA ] ;In this case, the two companies would appear in the same group since the rating value is identical for both of them. Alternatively, you could create a new class of rating values and assign each value to an entry in this table as illustrated below:
Entity createSubclass: "Rating" ; #-- code and name available Rating createInstance: "A" ; Rating createInstance: "B" ; Rating createInstance: "C" ; Named Company GM :rating <- Named Rating A ; Named Company IBM :rating <- Named Rating A ;In this case, you can also correctly group by the rating object.
The previous example often introduces a common programming inefficiency. Instead of grouping by the rating, users will often group by the rating code value. For example, the expressions:
Company masterList groupedBy: [ industry code ] . do: [ print ; groupList count printNL ] ;and
Company masterList groupedBy: [ industry ] . do: [ print ; groupList count printNL ] ;appear to work identically. The inefficiency arises if you wish to access additional information about the industry. For example, the two approaches are used below to also display the industry's name:
#--- Group by industry code and also display the name Company masterList groupedBy: [ industry code ] . do: [ #-- ^self is the string value of code at this point #-- you need to look up the code in the industry dictionary !ind <- ^global Named Industry at: ^self ; ind code print: 10 ; ind name print: 30 ; groupList count printNL ; ] ;and
#--- Group by industry and also display the name Company masterList groupedBy: [ industry ] . do: [ #-- ^self is the industry at this time #-- code and name are both directly available code print: 10 ; name print: 30 ; groupList count printNL ; ] ;The second approach is much cleaner than the first.
The groupedByString message can be used instead of groupedBy: to perform this query correctly.
See: Grouping Collections
When I send the "showMessages" message to a list I get the set of messages that all lists respond to. How do I know what the elements of the list respond to?
You really want the list of messages associated with the elements of the list. You could execute the showMessages message inside the list using:
myList do: [ ^self showMessages ] ; #-- Note: omitting the ^self will display the messages defined #-- inside the block - not what you wantThis technique will work but generates the output for all elements in the list. Since many or all of the list elements are probably from the same class, this produces a good deal of extraneous output. Assuming all the elements are from the same class, the expression:
myList at: 1 . showMessagescan be used to display the messages to which any element of the list will respond. To see what classes are actually present in the list, use the expression:
myList groupedBy: [ whatAmI ] . do: [ ^self print: 50 ; groupList count printNL ] ;
When and why do you have to copy variables using ^my?
Because the magic word ^my can only "reach back" one level of context, you need to copy variables when you create many nested context levels that all need access to information from prior levels. This usually happens when you perform many nested list or time series operations. For example, you could define a local variable x at each level of a 3-level grouping and display each of the values using the program:
myList quintileDown: [ sales ] . groupedBy: [ quintile ] . do: [ !level1 <- ^current ; !x <- 1 ; groupList groupedBy: [ industry sector ] . do: [ !level2 <- ^current ; !level1 <- ^my level1 ; !x <- 2 ; groupList groupedBy: [ industry ] . do: [ !level3 <- ^current ; !level2 <- ^my level2 ; !level1 <- ^my level1 ; !x <- 3 ; groupList do: [ !x <- quintile ; ticker print: 10 ; x print ; ^my level1 x print ; ^my level2 x print ; ^my level3 x print ; newLine print ; ] ; ] ; ] ; ] ;This program will display the values for x from each level for each company in each group. The actual values printed will be the same for all companies (i.e., 1, 2, and 3).
See: The Magic Word ^my