Vision Programs
Overview
Whether you realize it or not, you have already written your first Vision program. A Program is merely a set of one or more Vision expressions that are viewed together as a unit. Most of the programs you have seen so far have asked Vision to perform a single request. There are situations where you would like to perform a set of operations, one after another. Vision provides a simple mechanism for accomplishing this.
The ';' character is used to separate expressions within a Vision program. Each expression in a program is known as a Statement. Each statement is executed in sequence. Programs always return the value of the last statement executed. This value is automatically printed by Vision. For example, the following program can be used to calculate a value and print out a descriptive message as well as the result:
!sum <- 2 + 2 ; "The Answer Is: " print ; sum printNL ;This program contains 3 statements separated by the ; character. The first statement computes the sum of 2 + 2 and saves the result in the variable sum. The second statement prints the string containing the label "The Answer is: ". The final statement prints the sum followed by a carriage return.
Some expressions that can be written as a single expression, can be subdivided into more manageable pieces by creating multiple expressions. For example:
!count <- mylist union: yourlist . exclude: autolist . select: [ score > 20] . count ;could be rewritten as:
!ourlist <- mylist union: yourlist ; !universe <- ourlist exclude: autolist ; !optimalList <- universe select: [score > 20 ] ; !count <- optimalList count ;
Blocks
A Block is a program that represents a deferred sequence of actions. Syntactically, a block is a program surrounded by square brackets. The general form for a block is illustrated below:
[ statement 1 ; statement 2 ; . . . ]The block will be executed when it receives the unary message value. For example, the following two programs have the same effect:
!index <- 17 ; :index <- index + 1 ; index print ;and
!index <- 17 ; [:index <- index + 1 ] value ; index print ;Blocks always return the value of the last statement executed.
Suppose every month you create a new screening universe which consists of the union of two lists that have been created by two other individuals at your firm. You call this list currentList. You also produce a second list that represents the companies added to currentList. To accomplish this, you start out with two empty lists that will be used to hold a current list and a list of changes. You also create a block which will be used to update these lists:
!currentList <- List new ; !changeList <- List new ; !myProgram <- [ !oldList <- currentList; :currentList <- joesList union: marysList; :changeList <- currentList exclude: oldList; ] ;Each month, after Joe and Mary have updated their lists, you can execute this program by typing the expression:
myProgram valueYou can now access the updated values of the variables currentList and changeList.
You have actually already seen another use for blocks. In the expression:
universe select: [ score > 20 ]the argument to the select: message is actually a block. When universe receives the message select:, it evaluates the block once for each of the elements contained in the list.
Messages such as select: take advantage of the block's deferred execution. Essentially, the block is used to store the program to be executed without actually running it. The select: and other messages are defined to evaluate the block when necessary.
More detailed information about blocks is available.
Control Structures
Control Structures are used in all programming languages to control the order of expression evaluation. By default, all statements in a Vision program are evaluated sequentially. Blocks can be used to create new control structures that alter the flow of a program. These control structures are invoked either by sending a message to a block or by sending a message with one or more blocks as arguments.
TRUE and FALSE objects respond to several messages that support conditional execution:
- ifTrue: trueBlock ifFalse: falseBlock
- ifTrue: trueBlock ifFalse: falseBlock else: elseBlock
- ifTrue: trueBlock
- ifTrue: trueBlock else: elseBlock
- ifFalse: falseBlock
- ifFalse: falseBlock else: elseBlock
For example:
myList count > 100 ifTrue: [ "This is a big list." ] ifFalse: [ "This is a small list." ]If the number of elements in the list myList is greater than 100 the text "This is a big list" will be displayed; otherwise, the text "This is a small list" will be displayed.
The value returned by these messages is the value of the block that was executed. In the example:
!result <- myList count > 100 ifTrue: [ 100 ] ifFalse: [ -100 ] else: [ 0 ]the variable result will be assigned to the value 100, -100, or 0 depending on the number of elements in myList.
The ifTrue: or ifFalse: message can be used if there is only one conditional consequence of interest. For example:
count <= cutoff ifTrue: [ :total <- total + 100 ]This expression is identical to:
count <= cutoff ifTrue: [ :total <- total + 100 ] ifFalse: [ NA ]Since the arguments in these messages are blocks, they can contain complex expressions as well, including nested control structures as shown below:
myList count > 100 ifTrue: [ myList count > 1000 ifTrue: [ "This is a very big list." ] ifFalse: [ "This is a big list." ] ] ifFalse: [ "This is a small list." ]This program will return one of the 3 strings, depending on the number of elements in the list myList. Since there is no terminating ; at the end of this program, the returned string will print.
It is often useful to repeat an operation until a particular event is reached. This conditional repetition of an activity is provided by sending the whileTrue: message to a block with a second block as its argument. For example, this message could be used to print out the squares of the first ten numbers as shown below:
!maxCount <- 10 ; !count <- 1 ; [ count <= maxCount ] whileTrue: [ count print ; (count * count ) printNL ; :count <- count + 1 ; ] ;The whileTrue: message is defined to evaluate the recipient block. If it returns the value TRUE, the block supplied as a parameter is evaluated and the whileTrue: message is invoked again. Otherwise, the message terminates.
Warning!! The condition in the recipient block is a test on the value of the count variable which is updated in the block supplied as a parameter. The form for updating this variable is :count not !count. If you used !count to update the variable, you would actually be defining a new variable local to the block in which it is defined. As a result, the variable representing the counter would never get updated and your whileTrue: would never terminate.
You can write new Vision methods that create additional control structures. The whileTrue: message could be written as illustrated below:
Block defineMethod: [ | whileTrue: aBlock| !test <- ^self value ; #- evaluate the recipient test ifTrue: #- if it evaluates to TRUE [ aBlock value; #- evaluate the parameter ^self whileTrue: aBlock #- invoke whileTrue again ] #- otherwise do nothing ] ;
More detailed information about control structure messages is available.
Formatting Conventions
Formatting conventions are not that important when you are creating expressions that you are executing immediately; however, when you define programs that you plan to reuse, you should concentrate on developing a clear, consistent style so that you can read and modify them at a later date.
Programs can be created using spaces, tabs, and carriage returns wherever needed to enhance readability. For example, multiple keyword messages are often written with each keyword-argument pair on different lines, as shown below:
value within: 10 percentOf: oldvalueor
condition ifTrue: [ ] ifFalse: [ ] else: [ ]Upper and lower case letters are distinct characters in Vision. A convention evident in the examples above is that names formed by concatenating several English words capitalize each word following the first one. By convention, names that identify classes usually begin with an upper case letter. These conventions are not enforced by Vision.
The # character is used to begin a comment. All characters appearing on a line that follow this character are ignored. For example:
#################### # Sample Program Using Comments #################### !x <- 10 ; # create variable x to hold first value !y <- 5 ; # create variable y to hold second value x + y # print result
Blocks and Parameters
Blocks can be named by providing a valid selector enclosed within vertical bars (i.e., the '|' character) as shown below:
[ | sample | statement1 ; statement2 ; . . . ] ; [ | sampleP1: a P2: b | statement1 ; statement2 ; "Parameter 1: " print ; a print ; "Parameter 2: " print ; b print ; . . . ] ;The first example names the block sample. This is equivalent to defining a block with no parameters. This is the format used to define methods that do not require parameters.
The second example defines a block which requires 2 parameters to execute. You use the valueWith: message to evaluate a block with one parameter, the valueWith:and: to evaluate a block with two parameters, and so on. For example:
!block <- [ | sampleP1: a P2: b | "Parameter 1: " print ; a printNL ; "Parameter 2: " print ; b printNL ; ] ; block valueWith: 10 and: 5produces:
Parameter 1: 10 Parameter 2: 5The names a and b are Local Variables that are only available inside the block. They take on the values supplied when you run the method. You can use the variables within the block just like any other messages.
If a block is defined to require parameters and you do not supply the correct number, you will get an error message. For example, if you supplied the above block with a single value:
block valueWith: 10You would see the error message:
>>> Parameter 'a' Unavailable <<<Note that stand-alone blocks such as the one defined here do not utilize the full block name, in this case sampleP1:P2:. This name is important when the block is used to define methods.. If you are using the block as a stand-alone, you can use the following short-hand to define the block:
!block <- [ | :a :b | "Parameter 1: " print ; a printNL ; "Parameter 2: " print ; b printNL ; ] ;This block works exactly the same way as the previously named block in that it defines two parameters named a and b. Execution is identical to before:
block valueWith: 10 and: 5and produces the same results.
Writing Methods
The method defineMethod: requires a named block as its parameter. To define a method that requires no parameters, use the form:
object defineMethod: [ | messageName | statement1 ; statement2 ; ] ;For example, to define a method that prints the count of the number of elements in a list use:
List defineMethod: #-- name the method printCount with 0 parameters [ | printCount | count print ; ] ;This defines the method printCount as a message for the class List that requires 0 parameters. To run it use:
!myList <- 1, 2, 3 ; myList printCount ; #- prints 3To define a method that adds a supplied value to the list count and prints the result, use:
List defineMethod: #-- name the method printCountPlus: with 1 parameters [ | printCountPlus: n | (count + n) print ; ] ;This defines the method printCount: as a message for the class List that requires 1 parameter. To run it use:
!myList <- 1, 2, 3 ; myList printCountPlus: 5 ; #- prints 8To define a method that adds a supplied value to the list count, multiplies the result by a second supplied value, and prints the result, use:
List defineMethod: #-- name the method printCountPlus:times: with 2 parameters [ | printCountPlus: p1 times: p2 | ( (count + p1) * p2 ) print ; ] ;This defines the method printCount:times: as a message for the class List that requires 2 parameters. To run it use:
!myList <- 1, 2, 3 ; myList printCountPlus: 5 times: 2 ; #- prints 16More detailed information about messages in general and about method execution is available.