Search code examples
clojurefunctional-programminglispsicp

Pass multiple parameters function from other function with Clojure and readability issues


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)))

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