Search code examples
clojurerecordkeyword

Namespace qualified record field accessors


I've made the same dumb mistake many many times:

(defrecord Record [field-name])

(let [field (:feld-name (->Record 1))] ; Whoops!
  (+ 1 field))

Since I misspelled the field name keyword, this will cause a NPE.

The "obvious" solution to this would be to have defrecord emit namespaced keywords instead, since then, especially when working in a different file, the IDE will be able to immediately show what keywords are available as soon as I type ::n/.

I could probably with some creativity create a macro that wraps defrecord that creates the keywords for me, but this seems like overkill.

Is there a way to have defrecord emit namespaced field accessors, or is there any other good way to avoid this problem?


Solution

  • Because defrecords compile to java classes and fields on a java class don't have a concept of namespaces, I don't think there's a good way to have defrecord emit namespaced keywords.

    One alternative, if the code is not performance sensitive and doesn't need to implement any protocols and similar, is to just use maps.

    Another is, like Alan Thompson's solution, to make a safe-get funtion. The prismatic/plumbing util library also has an implementation of this.

    (defn safe-get [m k]
      (let [ret (get m k ::not-found)]
        (if (= ::not-found ret)
          (throw (ex-info "Key not found: " {:map m, :key k}))
          ret)))
    
    (defrecord x [foo])
    
    (safe-get (->x 1) :foo) ;=> 1
    
    (safe-get (->x 1) :fo) ;=>
    
    ;; 1. Unhandled clojure.lang.ExceptionInfo
    ;;  Key not found:
    ;;  {:map {:foo 1}, :key :fo}