Exploring Clojure with Factorial ComputationThis project demonstrates a variety of of Clojure language features and library functions using factorial computation as an example. Many Clojure tutorials (and CS textbooks, for that matter) use factorial computation to teach recursion. I implemented such a function in Clojure and thought: "why stop there?" | (ns factorials.core) |
Basics | |
The classic (and verbose) | (defn factorial-using-recur [n] (loop [current n next (dec current) total 1] (if (> current 1) (recur next (dec next) (* total current)) total))) |
| (defn factorial-using-reduce [n] (reduce * (range 1 (inc n)))) |
| (defn factorial-using-apply-range [n] (apply * (range 1 (inc n)))) |
| (defn factorial-using-apply-iterate [n] (apply * (take n (iterate inc 1)))) |
"Code is data, data is code" | |
Higher-order functions FTW. | |
This returns a function that you can later call to compute a factorial value when needed. Usage:
| (defn make-fac-function [n] (fn [] (reduce * (range 1 (inc n))))) |
Here we illustrate Clojure homoiconicity
using Our function is building a valid Clojure expression that we then
| (defn factorial-using-eval-and-cons [n] (eval (cons '* (range 1 (inc n))))) |
Similar to the previous function, but note the macro output.
(More information on the mysterious | (defmacro factorial-function-macro [n] `(fn [] (* ~@(range 1 (inc n))))) |
Parallel Computation | |
Using
Reminder: | (defn factorial-using-ref-dosync [n psz] (let [result (ref 1) parts (partition-all psz (range 1 (inc n))) tasks (for [p parts] (future (Thread/sleep (rand-int 10)) (dosync (alter result #(apply * % p)))))] (dorun (map deref tasks)) @result)) |
Using I found that Also: per Joy of Clojure (ยง11.3.5) this is not the best use case for Agents (particularly
the | (defn factorial-using-agent [n psz] (let [result (agent 1) parts (partition-all psz (range 1 (inc n)))] (doseq [p parts] (send result #(apply * % p))) (await result) @result)) |
pmap to the Rescue | |
If the previous code looked arduous, never fear. Enter: | |
Yes, it is | (defn factorial-using-pmap-reduce [n psz] (let [parts (partition-all psz (range 1 (inc n))) sub-factorial-fn #(apply * %)] (reduce * (pmap sub-factorial-fn parts)))) |
There is also Even though this works, it feels wrong having to jump through hoops with
| (defmacro factorial-using-pvalues-reduce [n psz] (let [exprs (for [p (partition-all psz (range 1 (inc n)))] (cons '* p))] `(fn [] (reduce * (pvalues ~@exprs))))) |
| (defmacro factorial-using-pcalls-reduce [n psz] (let [exprs (for [p (partition-all psz (range 1 (inc n)))] `(fn [] (* ~@p)))] `(fn [] (reduce * (pcalls ~@exprs))))) |
More Advanced Stuff | |
Rolling Your Own Lazy Sequences | |
In simpler times, we used functions like In fact, we can cut out the middleman and compute a lazy sequence of
factorials using | |
I find "top-down" style to be more readable, so I forward-declare my function that generates the lazy seq. | (declare facseq) |
We use | (defn factorial-using-lazy-seq [n] (nth (facseq) n)) |
Finally, the function to generate the lazy factorial sequence. In this case,
I've made it private to the namespace with | (defn- facseq ([] (facseq 1 1)) ([n v] (let [next-n (inc n) next-v (* n v)] (cons v (lazy-seq (facseq next-n next-v)))))) |
Trampoline | |
Here, I've created a factorial function for use with If you're not familiar with
Example usage:
A benefit of | (defn factorial-for-trampoline [n] (letfn [(next-fac-value [limit current-step previous-value] (let [next-value (* current-step previous-value)] (if (= limit current-step) next-value #(next-fac-n limit current-step next-value)))) (next-fac-n [limit previous-step current-value] #(next-fac-value limit (inc previous-step) current-value))] (next-fac-value n 1 1))) |
MultimethodsI also thought of a way to compute a factorial using multimethods (and some recursion). | |
First define a "struct" that has the fields (Yes, a tuple in the form of By the way, we actually just defined the Java class | (defrecord Factorial [n value]) |
To define a multimethod, first you define the dispatch function with The function argument | (defmulti factorial-using-multimethods (fn ([limit] true) ([limit {:keys [n]}] (< n limit)))) |
Our multimethod repeatedly dispatches to this function while Note how I had to overload this method to initialize our | (defmethod factorial-using-multimethods true ([limit] (factorial-using-multimethods limit (new Factorial 1 1))) ([limit fac] (let [next-factorial (-> fac (update-in [:n] inc) (update-in [:value] #(* % (:n fac))))] (factorial-using-multimethods limit next-factorial)))) |
We hit the multimethod function for | (defmethod factorial-using-multimethods false ([limit fac] (* limit (:value fac)))) |
Using Arrays | |
Clojure also supports operating on arrays, for when you absolutely, positively need performance (at the sacrifice of immutability).
(Of course, this approach requires allocating and initializing an
array of size | (defn factorial-using-areduce [n] (let [arr (long-array n)] (dotimes [i n] (aset-long arr i (inc i))) (areduce arr idx ret (long 1) (* ret (aget arr idx))))) |
Java Interop | |
Elsewhere we wrote a plain old Java class with a static method that computes factorials. We can still re-use that legacy code via Clojure's Java interop. | (defn factorial-using-javainterop [n] (example.Factorial/calculate n)) |
Taming Java ComplexityBut what if our Java team read Effective Java, 2nd Ed. and decided to use the Builder pattern? | |
We can use the And if you're working with | (import 'example.Factorial$Builder) (defn factorial-using-javainterop-and-pipeline [n] (-> (Factorial$Builder.) (.factorial n) .build .compute)) |
More on the Pipeline Macro | |
I found | |
Executing the following at the REPL | (clojure.walk/macroexpand-all '(-> (Factorial$Builder.) (.factorial n) .build .compute)) |
outputs | => (. (. (. (new Factorial$Builder) factorial n) build) compute) |
which is equivalent to | => (.compute (.build (.factorial (example.Factorial$Builder.) n))) |
Implementing Java Interfaces | |
Perhaps our Java team, which doesn't use Clojure, needs us to implement one of their API interfaces. | |
Here we use | (defn newFactorialComputer [n] (reify example.ValueComputer (compute [this] (factorial-using-reduce n)))) |
Finally... | |
Why not just use Incanter? (Duh!) | (require '[incanter.core :only factorial]) (defn factorial-from-incanter [n] (incanter.core/factorial n)) |
Epilogue: HALL OF SHAME | |
Here are some functions I wrote that "work" but have hidden defects or are just plain wrong. | |
defs Aren't Variables | |
After calling This is because | (defn factorial-using-do-dotimes [n] (do (def a 1) (dotimes [i n] (def a (* a (inc i))))) a) |
This approach using | (defn factorial-using-do-while [n] (do (def a 0) (def res 1) (while (< a n) (def a (inc a)) (def res (* res a))) res)) |
Abusing Atoms | |
An example using Atoms. I suspect one would never (ab)use atoms locally-scoped like this (although they work well for implementing closures). However, perhaps you could contrive an example using the | (defn factorial-using-atoms-while [n] (let [a (atom 0) res (atom 1)] (while (> n @a) (swap! res * (swap! a inc))) @res)) |
Recursive Agent Race Condition | |
Finally, an attempt to compute a factorial using an This approach sometimes fails due to a race condition between
the recursive (and asychronous)
| (defn factorial-using-agent-recursive [n] (let [a (agent 1)] (letfn [(calc [current-n limit total] (if (< current-n limit) (let [next-n (inc current-n)] (send-off *agent* calc limit (* total next-n)) next-n) total))] (await (send-off a calc n 1))) @a)) |