POSTS
Guess the Number, Part 2 - Computer's first guess, the REPL, and let
In this tutorial, we will get our feet wet with the Read-Eval-Print-Loop (or REPL for short) while creating the first part of our game, which is to let the computer guess the number that the player is thinking of.
The Logic
To play the game, the computer has to guess a number between 0 to 99. One approach could be for the computer to keep on picking a random number until it gets the answer, but we would like for it to always get the right answer eventually.
After each guess, the computer is given a hint by the player: whether the correct answer is bigger or smaller than the guess. We can take this into account by constraining the set of choices that the computer can make:
- Computer has to pick a number between 0 to 99
- Computer guesses 45
- Player says, “Bigger!”
- Computer has to pick a number between 45 to 99
- Computer guesses 60
- Player says, “Smaller!”
- Computer has to pick a number between 45 to 60
- Repeat
To make the computer even more efficient, we can pick the middle number in the range of possible choices, thus dividing the number of possible choices by half with each guess, in a process known as Binary Search.
Now, let’s begin coding!
Note: this game idea and solution comes from the excellent Land of Lisp.
Getting input from the player
When the computer makes a guess, we need to collect the player’s input on whether that guess was correct. We can do this using the read-line function.
Let’s test it out in our program. Open core.clj
in your text editor, and modify it to look like the following:
(ns guessnumber.core)
(defn -main []
(print "Type in some text: ")
(flush)
(read-line))
Run the program with lein run -m guessnumber.core
. Wait for the Type in some text:
prompt to appear, then enter some text. Your output should look like this:
$ lein run -m guessnumber.core
Type in some text: How now brown cow
"How now brown cow"
What just happened in our program? Looking at the -main
function, what we did was:
- print our
Type in some text:
prompt. The print function is similar to println, except that it does not create a new line afterwards. - flush the buffered output. We need to call this function so that our prompt appears before the user can enter input.
- Read input from the user. In Clojure, the last form within a function is treated as the return value for that function. Hence, when executing
-main
, we see the return value as “How now brown cow”.
We’ll need more complex Clojure constructs to finish the rest of our program. To understand how they work, let’s bring in a useful tool called the REPL.
Introducing the Read-Eval-Print-Loop
The Read-Eval-Print-Loop, or REPL, is an interactive tool for instant feedback when programming, and can be found in many programming languages like JavaScript or BASIC. In Clojure, the REPL is a “command prompt” where you type in a Clojure form and see the results of executing that form immediately. The REPL is also an environment, meaning that whatever changes you make (such as declaring a function) are available throughout your REPL session.
leiningen is capable of launching a REPL for you. Open a new command prompt, navigate to the guessnumber
project and type the following command:
lein repl
After a while, you should see something similar to the following in your console:
nREPL server started on port 52568
REPL-y 0.1.10
Clojure 1.5.1
Exit: Control+D or (exit) or (quit)
Commands: (user/help)
Docs: (doc function-name-here)
(find-doc "part-of-name-here")
Source: (source function-name-here)
(user/sourcery function-name-here)
Javadoc: (javadoc java-object-or-class-here)
Examples from clojuredocs.org: [clojuredocs or cdoc]
(user/clojuredocs name-here)
(user/clojuredocs "ns-here" "name-here")
user=>
You can begin typing code on the last line, where it says user=>
. I recommend keeping two console windows open while doing Clojure development: one containing the REPL, and the other to run leiningen commands.
Exploring the REPL with simple calculations
Let’s try a few calculations in the REPL. Mathematical operators like +
, -
, *
and /
are regular functions in Clojure, meaning that to execute a calculation you place the function name as the first element between round brackets, and provide the parameters in subsequent elements.
Here’s 5 + 2 + 3:
user=> (+ 5 2 3)
10
10 * (5 + 4):
user=> (* 10 (+ 5 4))
90
50 - (3 * 2):
user=> (- 50 (* 3 2))
44
Treating mathematical operators as regular functions takes some getting used to, since we’re usually taught the more familiar “in-fix” style of 1 + 2 + 3
.
The computer’s first guess
Recall that the computer needs to guess a number between 0 to 99, and each guess should divide the search space in half. We can achieve that with the calculation (99 - 0) / 2
, or translated into Clojure:
user=> (/ (- 99 0) 2)
99/2
The return value in this case is a rational number. Other programming languages such as JavaScript may give you a decimal number (49.5) in this scenario, but Clojure preserves the answer in a rational form to increase mathematical accuracy. Right now though, what we want is a whole number. We can use the int function to coerce the value:
user=> (int (/ (- 99 0) 2))
49
Another issue we’re facing is with the maximum and minimum numbers, currently 99
and 0
respectively. The problem is that they’re hard-coded but we would like to update their values over the course of the game. To solve this, let’s use let
, a special form in Clojure for creating “local symbol bindings”.
Creating bindings with let
First, enter this code into the REPL:
user=> (let [minimum 0 maximum 99]
#_=> (int (/ (- maximum minimum) 2)))
49
In this code, we bound two symbols, minimum
and maximum
, to the values 0
and 99
respectively. We can then use those names in the same calculation we made in the earlier section for determining the computer’s guess.
As mentioned, let is a “Clojure special form for creating local symbol bindings”. What does that sentence mean, exactly?
A Clojure special form is a form that’s given special treatment in Clojure. Instead of executing a form as a regular function, Clojure uses a different set of rules for each special form. Similar to how JavaScript has syntax such as “if”, “var” and so on, Clojure has its special forms which make up the “syntax” of Clojure.
A symbol is a Clojure identifier. For instance, -main
is the symbol assigned to the -main
function that we created earlier.
The act of binding allows us to assign symbols to Clojure forms, which can then be accessed within the scope of the binding.
Local, or lexical scope, just means that the symbols that we have assigned are only accessible within a limited context. In this case, they are accessible only within the let
form.
Back in core.clj
, let’s modify our -main
method to use let
:
(defn -main []
(println "Think of a number between 0 to 99 and I'll try to guess it!")
(let [minimum 0
maximum 99
guess (int (/ (- maximum minimum) 2))]
(println "My guess is:" guess)
(print "> ")
(flush)
(read-line)))
On line 5, we’ve bound the result of our calculation to guess
, which we can then access on line 6 where we print out the computer’s guess.
The result of running the program:
Think of a number between 0 to 99 and I'll try to guess it!
My guess is: 49
>
""
Since we’re not doing anything with the input, we can just press enter at the prompt to end the program.
Additional notes on the REPL
You can look up documentation for any function on the REPL using the doc function.
user=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'
nil
Sometimes, you may get an error when trying to access doc
.
user=> (ns my.own.namespace)
nil
my.own.namespace=> (doc +)
CompilerException java.lang.RuntimeException: Unable to resolve symbol:
doc in this context, compiling:(NO_SOURCE_PATH:1:1)
This is probably because the REPL is in a different namespace (recall that namespace are like prefixes for Clojure to locate functions and other code). The current namespace is always printed to the left of the =>
prompt. The default namespace is usually user
, which is a special namespace created for the REPL session only and comes automatically with handy functions like doc
. So if you find yourself in a different namespace, you need to import the doc
function yourself using the require function:
my.own.namespace=> (require '[clojure.repl :refer :all])
nil
my.own.namespace=> (doc +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
Returns the sum of nums. (+) returns 0. Does not auto-promote
longs, will throw on overflow. See also: +'
nil
The parameters that require
accepts can be a bit complicated. If the documentation seems hard to grasp, I suggest learning about require
and ns
only after you have a bit more Clojure exposure.
Another thing to note about the REPL: when running a REPL via lein repl
, the REPL launched is REPL-y, a third party REPL offering improvements over the standard Clojure REPL. It offers a few goodies such as ClojureDocs integration, history navigation and additional shortcut keys. You can read about these improvements on the REPL-y home page.
Summary
In this tutorial, we’ve discussed the logic behind Guess the Number, retrieved input from the player (but done nothing with it yet), played around with the REPL and learned about let
. Next time, we’ll look at more complex programming constructs. Specifically, how to handle branching conditions, and looping.
-
clojure