An Architect's View

CFML, Clojure, Software Design, Frameworks and more...

An Architect's View

Just For Kicks - Scala and Clojure

November 10, 2010 ·

Jack Widman posted a Scala snippet on Twitter today to calculate the sum of squares up to a given number:

def sumSquares(n:Int) =
    (0 /: List.range(1,n+1).map((i:Int)=>i*i)) (_ + _)

(I renamed the parameter of the anonymous function argument to map just to make it clear it's different to the parameter of sumSquares itself)

I responded with a version of it in Clojure for comparison:

(defn sum-squares [n]
    (reduce + (map #(* % %) (range 1 (inc n)))))

They're very similar: they both reduce (using +) over map (using * to square items) over a range of numbers from 1 to n+1 (inclusive at the bottom, exclusive at the top). Note that reduce is called /: in Scala and the function argument comes afterward.

To be a true apples-to-apples comparison, the Clojure version should be typed, i.e., the parameter n should be declared to be an integer:

(defn sum-squares [^Long n] ... )

That makes quite a difference to the performance.

CFML doesn't have collections so it's not easy to produce an equivalent version without writing all of the primitives (reduce, map, range) from scratch and even then it would only be easy to write for arrays - and it wouldn't be very efficient since ColdFusion copies arrays on assignment (Railo treats arrays as reference types for efficiency reasons).

Of course, most CFers would just write sumSquares in a straightforward procedural manner like this:

function sumSquares(n) {
    var total = 0;
    for ( var i = 1; i <= n; ++i ) {
        total += i * i;
    }
    return total;
}

This is radically different of course since it conflates reduce and map (in the body of the loop) and range has become a for-loop. This is why I tell people it's so valuable to learn other languages: they help you think about problems in a different way and you can become a better programmer because of that. It's why I recently bought Seven Languages in Seven Weeks by Bruce Tate. Of the seven languages, I know three fairly well (Clojure, Prolog and Scala) although it's been years since I did anything significant with Prolog. I've dabbled with Haskell and I had a brief look at Ruby a few years ago. I'm a couple of days into the Ruby week and it's challenging in a good way. Tate deliberately doesn't provide any hand-holding for installation or "getting started" basics (and he explains why in the book) so even the relatively simple real-world exercises require a bit of work and some reading up on your own. Definitely a good way to force you to learn. I'm looking forward to revisiting Prolog and going deeper into Haskell as well as getting started properly with Erlang, Io and Ruby!

Put the book on your Christmas list!

Tags: clojure · coldfusion · scala

3 responses

  • 1 Peter Boughton // Nov 11, 2010 at 4:35 AM

    >> it's not easy to produce an equivalent version without... <<

    Heh, I like a challenge. :)

    This isn't the same method, but to show it's still possible to do it with one-line of CFML:

       function sumSquares(n)
          { return n ? n^2 + sumSquares(n-Sgn(n)) : 0 ; }

    No idea how that performs, but no arrays involved and nothing complex used, so should be fine?


    For ACF8 and earlier there's no ternary operator, but can do this:
          { return n AND n^2 + sumSquares(n-Sgn(n)); }
  • 2 Steve // Nov 11, 2010 at 6:55 AM

    def sumSquares(n:Int) =
    (0 /: List.range(1,n+1).map((i:Int)=>i*i)) (_ + _)

    would be more succinctly (and legibly) written as

    def sumSquares(n: Int) =
    (1 to n + 1) map(i => i * i) sum
  • 3 Jacek Laskowski // Nov 12, 2010 at 10:35 AM

    A possible imperative-like solution with state monad would be as follows:

    (use 'clojure.contrib.monads)

    (defn sum-m
    [n]
    (let [value (* n n)
    next (dec n)]
    [value next]))

    (defn sum-squares-m-helper
    [n]
    ((domonad state-m
    [sum (m-reduce + (replicate n sum-m))]
    sum) n))

    (defn sum-squares-m
    [n]
    (first (sum-squares-m-helper n)))

    One could use sequence monad, but the result is a list so reduce is necessary:

    (defn sum-squares-m
    [n]
    (reduce +
    (domonad sequence-m
    [m (range (inc n))]
    (* m m))))

    Some might find these examples more explanatory (unlike monads themselves), but not as succinct as yours (alas).