Question

I'm trying to learn functional programming with SICP. I want to use Clojure.

Clojure is a dialect of Lisp but I'm very unfamiliar with Lisp. This code snippet unclean and unreadable. How to write more efficient code with Lisp dialects ?

And how to pass multiple parameters function from other function ?

(defn greater [x y z]
  (if (and (>= x y) (>= x z)) 
    (if (>= y z)
      [x,y]
      [x,z]) 
    (if (and (>= y x) (>= y z)) 
      (if (>= x z)
        [y,x]
        [y,z]) 
      (if (and (>= z x) (>= z y)) 
        (if (>= y x)
          [z,y]
          [z,x])))))

(defn sum-of-squares [x y]
    (+ (* x x) (* y y)))

(defn -main
  [& args]
  (def greats (greater 2 3 4))
  (def sum (sum-of-squares greats)))
Was it helpful?

Solution

You are asking two questions, and I will try to answer them in reverse order.

Applying Collections as Arguments

To use a collection as an function argument, where each item is a positional argument to the function, you would use the apply function.

(apply sum-of-squares greats) ;; => 25


Readability

As for the more general question of readability:

You can gain readability by generalizing the problem. From your code sample, it looks like the problem consists of performing the sum, of the squares, on the two largest numbers in a collection. So, it would be visually cleaner to sort the collection in descending order and take the first two items.

(defn greater [& numbers]
  (take 2 (sort > numbers)))

(defn sum-of-squares [x y]
  (+ (* x x) (* y y)))

You can then use apply to pass them to your sum-of-squares function.

(apply sum-of-squares (greater 2 3 4)) ;; => 25

Keep in Mind: The sort function is not lazy. So, it will both realize and sort the entire collection you give it. This could have performance implications in some scenarios. But, in this case, it is not an issue.


One Step Further

You can further generalize your sum-of-squares function to handle multiple arguments by switching the two arguments, x and y, to a collection.

(defn sum-of-squares [& xs]
  (reduce + (map #(* % %) xs)))

The above function creates an anonymous function, using the #() short hand syntax, to square a number. That function is then lazily mapped, using map, over every item in the xs collection. So, [1 2 3] would become (1 4 9). The reduce function takes each item and applies the + function to it and the current total, thus producing the sum of the collection. (Because + takes multiple parameters, in this case you could also use apply.)

If put it all together using one of the threading macros, ->>, it starts looking very approachable. (Although, an argument could be made that, in this case, I have traded some composability for more readability.)

(defn super-sum-of-squares [n numbers]
  (->> (sort > numbers)
       (take n)
       (map #(* % %))
       (reduce +)))

(super-sum-of-squares 2 [2 3 4]) ;;=> 25


OTHER TIPS

(defn greater [& args] (take 2 (sort > args)))

(defn -main
  [& args]
  (let [greats (greater 2 3 4)
        sum (apply sum-of-squares greats)]
    sum))

A key to good clojure style is to use the built in sequence operations. An alternate approach would have been a single cond form instead of the deeply nested if statements.

def should not be used inside function bodies.

A function should return a usable result (the value returned by -main will be printed if you run the project).

apply uses a list as the args for the function provided.

To write readable code, use the functions provided by the language as much as possible: e.g. greater can be defined as

(defn greater [& args]
   (butlast (sort > args)))

To make sum-of-squares work on the return value from greater, use argument destructuring

(defn sum-of-squares [[x y]] 
   (+ (* x x) (* y y)))

which requires the number of elements in the argument sequence to be known,

or define sum-of-squares to take a single sequence as argument

(defn sum-of-squares [args]
   (reduce + (map (fn [x] (* x x)) args)))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top