clojure - Leiningen command args parsing



I'm new with Clojure and I've a problem with leib command line args I can't go over. My app is quite simple:

 (defn -main
  [& args]
  (println (apply hash-map args))
  (let [{:keys [f w h]} (apply hash-map args)]
    (println f w h)
;     (init-frame w h)
;     (draw-values f w h)

The terminal output:

lein run :f bit-xor :w 200 :h 200
{:w 200, :h 200, :f bit-xor}
nil nil nil

When I run -main from REPL it works well. When I define a hashmap inside the core.clj like this is working well also.

(def my-args (hasmap :f "bit-xor" :w 200 :h 200))

I can't figure out, why my 'f', 'w' and 'h' in let get nil. Can anyone help?

I've made a special small app just for test.

 (ns cmdargs.core

(defn -main
 [& args]
 (println "args: " args)
 (println "args map: " (apply hash-map args))
 (println "param keys: " (keys (apply hash-map args)))
 (println "param vals: " (vals (apply hash-map args)))
 (let [{:keys [param1 param2]} (apply hash-map args)]
   (println "param1: " param1)
   (println "param2: " param2)))

The REPL output:

 cmdargs.core> (-main :param1 200 :param2 300)
 args:  (:param1 200 :param2 300)
 args map:  {:param2 300, :param1 200}
 param keys:  (:param2 :param1)
 param vals:  (300 200)
 param1:  200
 param2:  300

The terminal/lein output:

 cmdargs$ lein run :param1 200 :param2 300
 Java HotSpot(TM) Client VM warning: TieredCompilation is disabled in this release.
 Java HotSpot(TM) Client VM warning: TieredCompilation is disabled in this release.
 args:  (:param1 200 :param2 300)
 args map:  {:param1 200, :param2 300}
 param keys:  (:param1 :param2)
 param vals:  (200 300)
 param1:  nil
 param2:  nil

2 Answers: 

The :keys destructuring only works with keywords, and that what you think are keywords coming in through -main are not actually keywords but strings, each of which starts with a colon. To confirm this use the function type on the parameters that come in.

From I found this quote:

The :keys key is for associative values with keyword keys

If you do need to convert the incoming arguments to keywords then use keyword:

(keyword (subs ":f" 1))

So in your case you might do it like this:

(map (comp keyword #(subs % 1)) [":f" ":w" ":h"])

, but instead of [":f" ":w" ":h"] you would have args.

Of course things would become a bit simpler if you decided to omit the colons, which do not make sense outside the Clojure reader: .


Thanks! It was a little bit confusing that the REPL and Lein interpret differentially the 'args'.

The REPL interpret ':param1' in

-main :param1 200

into clojure.lang.Keyword

but in Lein

lein run :param1 200

into 'java.lang.String'.

I've tried :strs instead of :keys earlier but forgot the colon. This is working:

(defn -main
 [& args]
 (let [{:strs [:param1 :param2]} (apply hash-map args)]
 (println "param1: " param1)
 (println "param2: " param2)))

Of course, in this case colon before param's name doesn't mean anything.

For :keys I've done the following

(defn -main
  [& args]

  (let [{:keys [param1 param2]} 
     (into {} (for [[k  v] (apply hash-map args)]
                   [(keyword (apply str (rest k))) v]))]
  (println "param1: " param1)
  (println "param2: " param2)))