4
\$\begingroup\$

I wanted to wrap the jkeymaster library in a Clojure wrapper (for my own use, but perhaps also to save others some time). I'm just learning Clojure so I'm still not quite sure what "idiomatic libraries/APIs" look like. In Ruby, I would have created an object with methods, but in one of the clojure books they recommended letting users manage state themselves, so I have function that creates a provider, and this provider must then be passed to the register function.

Here's first an example of usage:

(ns keymaster.core
  (:require [keymaster.keymaster :as km]))
(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (km/register (km/provider) "control shift 1" #(println "hi")))

And here is the full text of the "library".

(ns keymaster.keymaster
  (:gen-class)
  (:use (com.tulskiy.keymaster.common)))

(defn provider []
  "Gets and initiates a keymaster provider, which must be passed to register to register shortcuts"
  (let [provider (com.tulskiy.keymaster.common.Provider/getCurrentProvider true)]
    (.init provider)
    provider))

(defn- conv-keystroke [x]
  "Takes keystroke in the form \"control shift 1\" and returns a Keystroke class"
  (javax.swing.KeyStroke/getKeyStroke x))

(defn- conv-listener [f]
  "Takes a function with one argument, which will get passed the keycode, and creates a listener
   Todo: How to accept a function with or without a parameter to accept hotKey?"
   (proxy [com.tulskiy.keymaster.common.HotKeyListener] [] (onHotKey [hotKey] (f))))

(defn register
  [provider shortcut listener]
  "Registers a shortcut on provider, which will trigger listener (with one argument)"
  (let [k (conv-keystroke shortcut)
        l (conv-listener listener)]
    (.register provider k l)))

In addition to any general style feedback, both about the coding, and the design of the API, I'm also wondering if there is any way to have conv-listener accept either a 0 or 1-arity function. Right now I made the choice of accepting a 0-arity function, but if someone wanted to pass a 1-arity function, where the first argument would be the name of the keycode called (hotKey), which I would call with (f hotKey), I would have to rewrite... Possible to allow both? (1-arity is inconvenient for anonymous functions where we really don't need it - which I suspect is most of the time, since we can bind different hotkeys to different listeners).

\$\endgroup\$
  • \$\begingroup\$ I rewrote slightly to have provider instead return a (partial register provider), which the user can then use to register a new key directly, thus exposing only one function to the user. github.com/houshuang/keymaster-clj - still interested in feedback \$\endgroup\$ – Stian Håklev Apr 30 '13 at 2:13
  • \$\begingroup\$ Got a nice GitHub pull request (another form of code review), and a substantially nicer function is now available - also as a Clojar. But still happy to receive feedback here. \$\endgroup\$ – Stian Håklev May 2 '13 at 2:53
2
\$\begingroup\$

As a wrapper goes I think this looks fine. The feedback I do have is that I wouldn't use let as often as you do and would use the treading macro (-> ...) instead.

For example:

(-> (com.tulskiy.keymaster.common.Provider/getCurrentProvider true)
      .init)

does the same as

(let [provider (com.tulskiy.keymaster.common.Provider/getCurrentProvider true)]
  (.init provider)
  provider)

and it's easier to read and shorter. You could also do it this way:

(.init (com.tulskiy.keymaster.common.Provider/getCurrentProvider true))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.