A comprehensive guide for Java developers to implement a basic calculator in Clojure, focusing on parsing user input and evaluating expressions safely.
In this section, we will walk through the process of creating a basic calculator in Clojure. This exercise will help you understand how to parse user input and evaluate expressions safely, leveraging Clojure’s functional programming paradigms. As an experienced Java developer, you will appreciate the differences and similarities in handling such tasks between Java and Clojure.
The calculator we will build will support basic arithmetic operations: addition, subtraction, multiplication, and division. The primary focus will be on:
Before diving into the implementation, ensure your development environment is ready. You should have Clojure installed along with a preferred editor or IDE configured for Clojure development. Refer to Chapter 3 for detailed setup instructions.
Parsing user input is a critical step in building a calculator. In Clojure, we can leverage its powerful sequence manipulation capabilities to parse and process input efficiently.
We’ll start by writing a function to accept user input. In a real-world application, this might come from a file, a web form, or a command-line interface. For simplicity, we’ll assume input is provided as a string.
(defn get-user-input []
(println "Enter an arithmetic expression:")
(read-line))
Once we have the input, the next step is to tokenize it. Tokenization involves breaking the input string into meaningful components (tokens) such as numbers and operators.
(defn tokenize [input]
(clojure.string/split input #"\s+"))
This function uses Clojure’s clojure.string/split
to divide the input string into tokens based on whitespace.
After tokenizing, we need to convert these tokens into a form that can be evaluated. We’ll write a function to parse the tokens into a list of operands and operators.
(defn parse-tokens [tokens]
(map #(if (re-matches #"\d+" %) (Integer/parseInt %) %) tokens))
This function checks each token to see if it matches a digit pattern. If it does, it converts the token to an integer; otherwise, it leaves it as an operator.
With the input parsed into a list of operands and operators, we can now evaluate the expression. Safety is crucial here to handle errors such as division by zero or invalid input gracefully.
We’ll define a function that takes a list of parsed tokens and evaluates the expression. This function will use a simple recursive approach to process the expression.
(defn evaluate-expression [tokens]
(let [operator (first tokens)
operands (rest tokens)]
(cond
(= operator "+") (reduce + operands)
(= operator "-") (reduce - operands)
(= operator "*") (reduce * operands)
(= operator "/") (try
(reduce / operands)
(catch ArithmeticException e
(println "Error: Division by zero")
nil))
:else (println "Error: Unknown operator" operator))))
This function uses cond
to determine the operation based on the first token (operator) and applies it to the rest of the tokens (operands). The division operation is wrapped in a try-catch
block to handle division by zero.
To ensure robustness, we need to handle various edge cases, such as:
(defn safe-evaluate [input]
(let [tokens (tokenize input)
parsed-tokens (parse-tokens tokens)]
(if (empty? parsed-tokens)
(println "Error: No input provided")
(evaluate-expression parsed-tokens))))
This function combines the previous steps and adds checks for empty input.
Let’s combine all the functions into a complete program that reads an expression from the user, parses it, evaluates it, and prints the result.
(defn calculator []
(let [input (get-user-input)]
(safe-evaluate input)))
(calculator)
Testing is crucial to ensure our calculator works as expected. We can write unit tests using Clojure’s built-in testing framework, clojure.test
.
(ns calculator-test
(:require [clojure.test :refer :all]
[calculator :refer :all]))
(deftest test-evaluate-expression
(testing "Addition"
(is (= 5 (evaluate-expression ["+" 2 3])))
(is (= 0 (evaluate-expression ["+" 0 0]))))
(testing "Subtraction"
(is (= 1 (evaluate-expression ["-" 3 2])))
(is (= -2 (evaluate-expression ["-" 0 2]))))
(testing "Multiplication"
(is (= 6 (evaluate-expression ["*" 2 3])))
(is (= 0 (evaluate-expression ["*" 0 3]))))
(testing "Division"
(is (= 2 (evaluate-expression ["/" 6 3])))
(is (nil? (evaluate-expression ["/" 1 0])))))
(run-tests)
Building a basic calculator in Clojure provides valuable insights into functional programming and expression evaluation. By focusing on parsing and safe evaluation, you can create robust applications that handle user input gracefully.
For further exploration, consider extending the calculator to support more complex expressions, including parentheses and operator precedence, or integrating it into a larger application.