CS 214 Lab 1: Clojure


In this part of the exercise, we will write our first functional-language program that will solve the same problem as circle_area.java.

Unlike traditional languages, LISP and its descendents are functional languages: languages in which all computation is accomplished by calling functions and passing them arguments (which can be other functions). The particular variety of LISP we will be studying is Clojure, a modern (2007) LISP dialect that modernizes the LISP language, and adds object-oriented and concurrent/parallel features to it. Clojure runs on the Java Virtual Machine, and (as we shall see) it supports interoperability with the Java language. Clojure has been used in real-world systems at a variety of companies, ranging from Apple to Walmart. You may find it convenient to bookmark this Clojure Cheat Sheet so that you can access it conveniently.

There are two ways to use Clojure:

We will briefly look at the first approach, and then take a deeper look at the second.

Using the REPL Interpreter

The clj REPL interpreter provides a quick and convenient means of experimenting with Clojure functions. To try it out, open a terminal window and enter:

   clj
After showing the version number, you should see a prompt like this:
   user=>
You can enter any valid Clojure expression and Clojure will (i) read the expression, (ii) evaluate the expression, (iii) print the result returned by the expression, and (iv) loop back to display the prompt again. Let's try it; enter this:
user=> (println "Welcome to Clojure!")
This expression invokes the println() function, and passes it the string Welcome to Clojure!" as an argument. The interpreter reads it, evaluates it, which causes "Welcome to Clojure" to appear followed by a newline, and then prints the value returned by the println() function, which is nil.

So far so good! Try this:

user=> (print "Welcome to Clojure!")
What is different from before? Does print() return the same value as println(), or a different value? If you aren't sure, talk to your neighbor and see if you can reach a consensus.

Note that where a function-call in a traditional language like Java has the form:

   functionName( arguments)
a function-call in Clojure follows this pattern:
   (functionName arguments)
You just position the open-parenthesis before the name of the function instead of after it.

In Clojure, operators are functions. Try entering this:

user=> (+ 1 2)
When you enter that expression, Clojure reads it, evaluates it (which calls the + function with 2 and 3 as arguments), and prints the result that the function returns: 3.

Next, try entering the equivalent expression the "normal" way:

user=> (1 + 2)
You should see an error message. (Get used to it; this will happen a lot.)

The problem is that this expression does not follow the pattern: (1 + 2) is trying to call a function named 1 and pass it + and 2 as arguments. Since there is no function named 1, Clojure displays that lovely error message.

Next, try entering this:

user=> (+ 1 2 3 4)
Perhaps surprisingly, this works correctly, producing the result 1+2+3+4 = 10! For convenience, many of the arithmetic operatators in Clojure allow you to enter an arbitrary number of arguments.

Note also that with clj, you can use the arrow keys:

Try this out:
  1. Use Up-arrow to re-display several of your previous expressions.
  2. Then use Down-arrow to get back to the (+ 1 2 3 4) expression.
  3. Use the Left-arrow (and/or the Right-arrow), plus the Backspace or Delete keys to change (+ 1 2 3 4) into (+ 1 2 3 4 5).
  4. Press Enter.
You can enter arbitrarily complicated expressions, but you have to follow the pattern. For example, to compute (1 + 2) * (3 - 4), you would enter:
user=> (* (+ 1 2) (- 3 4))
Note how, as you enter each close-parenthesis, the cursor "bounces" to the matching open-parenthesis. This can help you keep from entering expressions with mismatched parentheses.

One last thing: enter this:

user=> (* (+ 1 2) (- 3 4)
Note how clj nothing happens. The problem is that you have not finished the expression. Type the missing close-parenthesis, press Enter again, and you should see the same result as before. The expressions you enter can thus span multiple lines; a multi-line expression like this:
user=> (* 
(+ 1 2)
(- 3 4)
)
will work just fine.

To exit clj, type Ctrl-d (hold down the Ctrl key, press the d key once, and then release the Ctrl key). This should return you to the command-line prompt in your terminal.

Using a Text Editor and the Clojure Compiler

The clj REPL system is convenient for quickly trying out Clojure functions to see how they behave, but it isn't a very convenient way to enter full programs. For that, we will use a text editor and the clojure compiler.

Clojure source programs are simple text files, but Clojure is a bit particular about where you store those files, because it likes to keep the files related to a given Clojure product separate from the files for other projects.

In a terminal window, navigate to your 214/labs/01 folder. Inside your 01 folder, create a clojure folder; then create a src folder inside your 01/clojure folder. The Clojure source file you will create for this lab exercise must reside in this src folder. You will need to do this same procedure for each lab exercise and homework project.

Clojure source programs consist of text files whose names end in the .clj suffix, so in your terminal/console window, make certain you are "in" your 214/labs/01/clojure/src folder. (If you are not sure, enter the pwd command, which stands for path-to-working-directory.)

You can use any text editor to enter a Clojure source program, but make certain it does parentheses-matching. To illustrate using vim, we can create and edit a source file named circle_area.clj by entering:

   vim circle_area.clj
This will launch vim within your terminal window and create an empty text file named circle_area.clj in your current working directory (i.e., 214/labs/01/clojure/src).

And now, on to our program!

The Clojure Program

Copy the following program into your circle_area.clj file. As usual, be sure to personalize the opening documentation.


;;;; circle_area.clj calculates the area of a circle.
;;;;
;;;; Input: The radius of a circle.
;;;; Output: The area of that circle.
;;;;
;;;; Usage: clojure -m circle_area
;;;;
;;;; Begun by: Prof. Adams, for CS 214 at Calvin College.
;;;; Completed by:
;;;; Date:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(ns circle_area) ; namespace function names the program

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Function circleArea() computes the area of a circle.
;;; Receive: itsRadius, a number.
;;; Precondition: itsRadius >= 0.0.
;;; Return: the area of the corresponding circle.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn circleArea [itsRadius]
  (* Math/PI (* itsRadius itsRadius) )  ; return PI*r^2
)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Function -main is where execution begins
;;; Input: the radius of a circle.
;;; Output: the area of that circle.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn -main []
  (println "\nTo compute the area of a circle,")
  (print   " enter its radius: ") (flush)
  (let
    [ radius (read) ]

    (assert (>= radius 0) "-main: radius must be positive")
    (printf "\nThe area is %f\n\n" (circleArea radius))

    (print "\nThe area is ")
    (print (circleArea radius))
    (print "\n\n")

    (printf "\nThe area is %.15f\n\n" (circleArea radius))
  )
)  
This program consists of two functions, one named circleArea() and one named -main(), where execution will begin. Since Clojure is probably the most unusual of our four languages, let's spend some time studying the code carefully, as we can learn several things from it:

Take a few moments to locate and study each of these points, as understanding them will help you on this week's project.

When you are done studying the code, save the file and continue.

Building and Running Your Program

When working with a text editor, there are different ways to build and run your program. One way to run your (saved) program that does not require your to quit the text editor is to open a second terminal/console window, navigate to your 214/labs/01/clojure/ folder (the folder "above" your src folder), and there enter:

   clojure -m circle_area

This invokes the clojure compiler which will compile everything in the local src folder. The switch -m circle_area tells the compiler to run the -main function that is defined within the circle_area namespace.

Using this method, test what you have written using the same test values as before:

   1

   2

   2.5

   4.99999

Make certain that your results are equivalent to those produced by the other languages before continuing.

How do the print, println, and printf functions differ?

When displaying a real number, how can you control the number of decimal digits displayed by printf?

Finally, note that the Clojure Style Guide recommends several different "levels" of comments, each of which should appear immediately before the thing being commented on:

Clojure also provides a comment() function that can be used for block-comments. To use it, you can just put

(comment
before a chunk of code you want to comment out, and then put a close parenthesis at the end of that chunk. We won't use this in these exercises, but it is available to you if you find you need to use it. If you do, one thing to keep in mind is that the comment() function returns nil, so if you use it to comment out the body of a function:
   (defn aFunction [ itsArg ]
     (comment
       function body
     )
   )
and then call that function, its return-value will be nil because that is the value comment() returns.

Clojure also supports docstrings -- documentation comments that are enclosed within double-quotes -- which must appear immediately after the first line of a function. We have not used any of these in this exercise and will not use them in this course, but you will often see them in examples you find on the Internet, as they are used to auto-generate HTML documentation for function libraries being publicly distributed.

Wrapping Up

To create a recording named script.clojure that shows your program running correctly, go to your second terminal window (where your working directory is 214/labs/01/clojure/) and...
  1. Enter script script.clojure to start script running and recording its information in a file named script.clojure.
  2. Enter cat src/circle_area.clj to list your program.
  3. Enter clojure -m circle_area as many times as needed to run your program for each of the values shown above, to show that it works correctly for each.
  4. Enter exit or type Cntl-d to terminate the script program.
  5. Since your working directory is 214/labs/01/clojure/, enter mv script.clojure .. to move your script.clojure "up" to the parent directory 214/labs/01/ (with your other script files).

Return to the lab 1 page to complete the other parts of this exercise.


Calvin > CS > 214 > Labs > 01 > Clojure


This page maintained by Joel Adams.