What I do not like
The first thing that one faces when trying to use Clojure is the difficulty in installing it. The downloads page only lists a few zip files, without saying anything about how to use them. (Sure, there is a Readme file, but I would have like to know something more before downloading anything.) In comparison, the Downloads page of Scala is much better in shape, and a number of pre-built packages are provided. Moreover, Scala is included in Macports, while Clojure is not. Once the ZIP file has been uncompressed, the Readme file is extremely laconic: it just reports the command required to run/build Clojure. Since the REPL has no editing facilities (i.e. you cannot use Backspace/delete to modify what you have written, and there is no history), I would have expected some reference to jLine or rlwrap as these can really save you the day.In general, Clojure shows its young age in a number of other spots. The documentation is quite sparse, and you often have to look at the source code of some function in order to understand its behavior. Many tools I regularly use for source code (e.g. Vim, Source-highlight and others) do not have native support for Clojure (although you can usually find something in the Web - I find VimClojure one of the best Vim plugins I have ever seen, the "rainbow parentheses" mode is incredibly useful!).
What I like
Being a LISP dialect, Clojure can be extremely elegant. Here is some code I wrote to solve problem 34 from Project Euler.(defn fact | |
"Return n!." | |
[^Integer n] | |
(if (< n 2) | |
1 | |
(apply * (range 2 (+ n 1))))) | |
(def fast-fact | |
; This is a faster version of "fact", using a dictionary to return the | |
; factorial for any number between 0 and 9 (the only kind of number for which | |
; we want the factorial here). | |
(reduce (fn [dict n] (assoc dict n (fact n))) {} (range 0 10))) | |
(defn digits | |
"Return a list of the digits of n." | |
[^Integer n] | |
(map #(- (int %) 48) (.toString n))) | |
(defn test-number | |
"Check if n is equal to the factorials of its digits." | |
[^Integer n] | |
(let [factorials fast-fact] | |
(= n (apply + (map fast-fact (digits n)))))) | |
(defn solution | |
"Return the solution for the problem." | |
[^Integer max-n] | |
(apply + (filter test-number (range 10 max-n)))) |
A few things worth to note:
- Clojure implements a large number of high-level data structures, like dictionaries. I used the latters in fast-fact, a dictionary whose keys are the 10 digits and whose values are the corresponding factorials.
- It is really easy to call Java functions like toString: just prepend them with a dot. No need to import modules to do this!
- Although Clojure is a dynamically typed language (like e.g. Python), it allows to specify the type of the input parameters for functions and let bindings. I have verified that this allows to dramatically increase the speed of the code (3x or 4x times).
- Like Python, Clojure allows to use docstrings. They can be accessed from the REPL through the doc function.
Python vs. Clojure
Since I am an avid Python user, I immediately rewrote the program in Python:def fact (n): | |
"Return n!." | |
if n < 2: | |
return 1 | |
else: | |
result = 1 | |
for i in xrange (2, n + 1): | |
result = result * i | |
return result | |
fast_fact = {} | |
def compute_fast_fact (): | |
"Initialize `fast_fact'." | |
for n in xrange (0, 10): | |
fast_fact[n] = fact (n) | |
def digits (n): | |
return [int(x) for x in list (str (n))] | |
def test_number (n): | |
return n == sum ([fast_fact[digit] for digit in digits (n)]) | |
def solution (max_n): | |
return sum ([num for num in xrange (10, max_n) if test_number(num)]) |
I tried to follow the same logic used in the Clojure program, i.e. using a dictionary for the first 10 factorials and getting the list of digits for a number by first converting it to a string (an alternative would be to repeatedly apply division and modulus 10). Then, using the Clojure time function and the IPython %time command, I was able to record the times used by each program. In order to get sounder values, I repeated each measurement till the timing did not change significatively any more.

No comments:
Post a Comment