Search code examples
clojure

Clojure-coding standards-for invoking functions with many arguments


I am currenly using a function

(def mymap {})

(defn function1 [var1 var2 var3 var4 var5]
  ;calls another functions with all variables.
  (function2 var1 var2 var3 var4 var5)
)

But as this is having more parameters I would like to convert this to a map before calling functions2.

(function2((assoc mymap (keyword var1) var1
               (keyword var2) var2
               (keyword var3) var3
               (keyword var4) var4
               (keyword var5) var5 ))
  )

Is this the correct way? Do we have better way to do this(In java we direclty use some objects in this scenario)


Solution

  • For general function args, I always go in order from the biggest to the smallest, in either size or "importance" (somewhat subjective).


    However, if you have more than 3 args, I prefer to pass a map containing the args and appropriate keyword names.

    The Tupelo Clojure library has some tools to make this easy. The macro vals->map takes multiple variable names and constructs a map from the (keywordized) variable name to its value, like:

      (let [ctx (let [a 1
                      b 2
                      c 3
                      d 4
                      e 5]
                  (t/vals->map a b c d e))]
        (is= ctx {:a 1 :b 2 :c 3 :d 4 :e 5})
    

    The macro with-map-vals does the opposite, deconstructing map values into local variables named for their keys. It is similar to the Clojure :keys destructuring, but in (IMHO) a more natural form:

        (let [{:keys [a b c d e]} ctx]    ; Clojure destructing syntax
          (is= [a b c d e] [1 2 3 4 5]))
    
        (t/with-map-vals ctx [a b c d e]
          (is= [a b c d e] [1 2 3 4 5])
          (is= 15 (+ a b c d e)))
    
        (t/with-map-vals ctx [b a d c e]  ; order doesn't matter
          (is= [a b c d e] [1 2 3 4 5])
          (is= 15 (+ a b c d e)))
    
        (t/with-map-vals ctx [b a d]      ; can ignore stuff you don't care about
          (is= [d a b] [4 1 2]))
    
        (throws?
          (t/with-map-vals ctx [a b z]    ; throws if key doesn't exist
            (println "won't ever get here")))))
    

    If you have nested data in maps and/or vectors, you can use the more powerful destruct and restruct tools. Here is a brief example (from the unit tests):

      (let [info  {:a 1
                   :b {:c 3
                       :d 4}}
            mania {:x 6
                   :y {:w 333
                       :z 666}}]
    
        (t/it-> (t/destruct [info {:a ?
                                   :b {:c ?
                                       :d ?}}
                             mania {:y {:z ?}}] ; can ignore unwanted keys like :x
                  (let [a (+ 100 a)
                        c (+ 100 c)
                        d z
                        z 777]
                    (restruct-all)))
          (t/with-map-vals it [info mania]
            (is= info {:a 101, :b {:c 103, :d 666}})
            (is= mania {:x 6, :y {:w 333, :z 777}})))
    

    As you can see, a question mark ? will cause the corresponding value to be destructed into a variable named for the corresponding keyword. It is also possible to create explicit variable names like so:

    (dotest
      (let [info  {:a 777
                   :b [2 3 4]}
            mania [{:a 11} {:b 22} {:c [7 8 9]}]]
        (let [z ::dummy]
          (t/it-> (t/destruct [info {:a z
                                     :b [d e f]}
                               mania [{:a ?} BBB {:c clutter}]]
                    (let [clutter (mapv inc clutter)
                          BBB     {:BBB 33}
                          z       77
                          d       (+ 7 d)]
                      (restruct-all)))
            (t/with-map-vals it [info mania]
              (is= info {:a 77, :b [9 3 4]})
              (is= mania [{:a 11} {:BBB 33} {:c [8 9 10]}]))))))
    

    It also works for mixed maps & vectors:

      (let [data {:a [{:b 2}
                      {:c 3}
                      [7 8 9]]} ]
        (t/destruct [data {:a [{:b p}
                               {:c q}
                               [r s t]]} ]
          (is= [2 3 7 8 9] [p q r s t])))