As usual, begin by making a clojure folder in your 214/labs/06/ folder; then create a src folder inside clojure. Then copy the source program skeleton split.clj from the class directory into your src folder.
Use a text editor to open the file and take a few minutes to study it, to see how it implements our basic algorithm. Note that the -main() function calls a function named split() to perform step 2 of our algorithm, but the definition of that function is missing.
To write the split() subprogram to perform step 2 of our algorithm, we need some means for that subprogram to send back two pieces of information: the two parts of the string being split.
Recall that LISP-family languages are called functional languages. One reason is that subprograms in LISP-family languages represent pure mathematical functions, meaning
FunctionDef ::= (defn identifier [Parameters]
Documentation
ExpressionList)
Parameters ::= identifier Parameters | Ø
Documentation ::= "Characters" | Ø
ExpressionList ::= Expression ExpressionList | Ø
Clojure is like Java, in that all arguments are passed by value,
but the values passed are references to objects,
and Clojure objects are generally immutable (i.e., unchangeable).
One way to attack this is to refine our problem specification slightly:
Receive: aString, a string,
pos, an integer.
Return: A vector whose values are two strings:
firstPart, the substring of aString prior to pos,
secondPart, the substring of aString following pos.
That is, since a LISP-family function can only return a single object,
our function can build a single, 2-element vector object,
whose elements are the two strings we wish it to return,
and then have our function return this vector.
The caller of the function can then extract the parts of the vector using the
Clojure get function, as seen in the -main function.
Begin by using the information above to build a function stub named split() in the appropriate place within split.clj. Since the return-value of a LISP-family function is the result of the last expression it computes, we might adjust our algorithm as follows:
1. Compute firstPart, the substring of aString prior to position.
2. Compute secondPart, the substring of aString from position
until the end of aString.
3. Build a vector consisting of firstPart and secondPart.
Steps 1 and 2. To accomplish steps 1 and 2, we can use a let function to define firstPart and secondPart as local variables. The syntax of let can be defined as:
LetFunction ::= (let [ Bindings ] ExpressionList ) Bindings ::= Binding Bindings | Ø Binding ::= identifier ValueAn identifier defined via a Binding in a let function is a local variable whose value is the specified Value. Using this information, add a let function to your split() stub that declares firstPart and secondPart (but don't worry about their Values just yet. You may want to use the let function in -main() as a model...
To provide variables firstPart and secondPart with Values, we need a function to compute a substring of our aString parameter. This operation is predefined in Clojure via the subs function:
SubStringOp ::= (subs identifier Start Stop) Start ::= IntExpression Stop ::= IntExpression | ØThis subs() function returns the substring of the string named identifier, beginning at Start, and ending at (i.e., not including the character at) Stop. The string's characters are indexed beginning at 0. If Stop is omitted, the last position in the string is the default. Using this information, add calls to your let function so that the initial values of firstPart and secondPart are the appropriate substrings of aString.
Step 3. To accomplish step 3 of our algorithm in Clojure, we can use the vector function, whose form is:
VectorFunction ::= (vector ExpressionList )When executed, the vector function builds a vector containing the expressions in ExpressionList, and returns the resulting vector. By placing such a call at the very end of our let function, we can cause it to build and return a vector consisting of the values of firstPart and secondPart. If let is the last function called by split(), and vector is the last function called by let, then the vector built by vector will be the return-value of split().
Drawing on these observations, finish the definition of function split(). You should be able to do this by adding a single function call (with the appropriate arguments).
Save your changes test what you have written for correctness, using the input string hello and the position values 0, 3 and 5. Make certain split.clj works correctly for all of these values before proceeding. If you have trouble, try putting each closing parentheses on a separate line, indented exactly as much as its opening parenthesis, as is the case in the -main() function. This isn't the usual Clojure style, but it can be very helpful in determining which functions are inside vs outside other functions.
When split.clj is working correctly, use script to create a new script.clojure file in which you use cat to list the source file, show that it compiles and runs without errors, and produces correct results, given the input string hello and the position-values 0, 3 and 5. Quit script and move the script.clojure file "up" to your clojure folder's parent folder (with the other script files).
That concludes the Clojure part of this lab.
Calvin > CS > 214 > Labs > 06 > Clojure