OpenSauce
OpenSauce

Reputation: 8633

Access clojure type hints at runtime

Suppose I define a record including some type hints, like so:

(defrecord person [name sex ^Integer age city])

Is there any way to determine at runtime which type hints were specified, using either the person class or an instance of person? The purpose is to change the gui component used, depending on the type of the field (note that the value of the field might be nil, so I can't use the type of the value to determine the type of the field).

I tried some obvious things, but didn't get anywhere:

; no metadata on the class, an instance, or the keys or vals of an instance
=> (meta person)
nil
=> (meta (person. "Geoff" "male" 30 "Moon base"))
nil
=> (map meta (keys (person. "Geoff" "male" 30 "Moon base")))
(nil nil nil nil)
=> (map meta (vals (person. "Geoff" "male" 30 "Moon base")))
(nil nil nil nil)
; the field is of type Object
=> (filter (fn [x] (= "age" (.getName x))) (.getFields person))
(#<Field public final java.lang.Object matt.clarity.scratch.person.age>)
; no metadata on the fields of the class
=> (map meta (.getFields person))
(nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil)

Upvotes: 3

Views: 305

Answers (3)

ordnungswidrig
ordnungswidrig

Reputation: 3186

There is no simple way to access type hints at runtime. You have to wrap defrecord in your own macro which can access the hints like that:

(->> (-> '(defrecord Foo [^Integer a b c]) 
         macroexpand-1
         (nth 2)
         (nth 3))
     (drop-last 2)
     (map #(vec [% (-> % meta :tag)]))) ; => ([a Integer] [b nil] [c nil])

However, I'd rather suggest to go with @mikera's answer and build a macro that emits a defrecord and a defvar which holds the field->type mappings.

Upvotes: 1

mikera
mikera

Reputation: 106401

I think you should approach this from a different angle:

  • Create a map which defines the fields and data types
  • Use this map to both generate the appropriate defrecord and any viewers / property editors

The map might look something like:

  {:name java.lang.String
   :sex  java.lang.String
   :age  java.lang.Integer}

This map then works as your metadata which drives the rest of your system.....

Upvotes: 3

Ankur
Ankur

Reputation: 33657

It seems that applying type hints on record fields doesn't have any effect on the reflection and hence they are ignored.

user=> (set! *warn-on-reflection* true)
user=> (defrecord user [^String name])     
user=> (.indexOf "z" (:name (user. "Superman")))
Reflection warning, NO_SOURCE_PATH:16 - call to indexOf can't be resolved.
-1

Also, I won't suggest using type hints for something that is related to your business logic, I would prefer to have my own tags with data to describe the type of data to be shown in UI controls. May be define a protocol that have methods to get the type of fields for a particular type and then implement that protocol on you records while defining the records.

Something like:

user=> (defprotocol DescribeData (getFieldTypes [this]))
user=> (defrecord User [name age] DescribeData (getFieldTypes [_] {:name String :age Integer}))
user=> (def u (User. "Superman" 1000))
user=> (getFieldTypes u)
{:name java.lang.String, :age java.lang.Integer}

This way you can pass any object to the UI layer and UI layer will work with any object that implement the protocol.

Upvotes: 2

Related Questions