Reputation: 8633
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
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
Reputation: 106401
I think you should approach this from a different angle:
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
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