(defrecord name [& fields] & opts+specs)
(defrecord name [fields*] options* specs*) Options are expressed as sequential keywords and arguments (in any order). Supported options: :load-ns - if true, importing the record class will cause the namespace in which the record was defined to be loaded. Defaults to false. Each spec consists of a protocol or interface name followed by zero or more method bodies: protocol-or-interface-or-Object (methodName [args*] body)* Dynamically generates compiled bytecode for class with the given name, in a package with the same name as the current namespace, the given fields, and, optionally, methods for protocols and/or interfaces. The class will have the (immutable) fields named by fields, which can have type hints. Protocols/interfaces and methods are optional. The only methods that can be supplied are those declared in the protocols/interfaces. Note that method bodies are not closures, the local environment includes only the named fields, and those fields can be accessed directly. Method definitions take the form: (methodname [args*] body) The argument and return types can be hinted on the arg and methodname symbols. If not supplied, they will be inferred, so type hints should be reserved for disambiguation. Methods should be supplied for all methods of the desired protocol(s) and interface(s). You can also define overrides for methods of Object. Note that a parameter must be supplied to correspond to the target object ('this' in Java parlance). Thus methods for interfaces will take one more argument than do the interface declarations. Note also that recur calls to the method head should *not* pass the target object, it will be supplied automatically and can not be substituted. In the method bodies, the (unqualified) name can be used to name the class (for calls to new, instance? etc). The class will have implementations of several (clojure.lang) interfaces generated automatically: IObj (metadata support) and IPersistentMap, and all of their superinterfaces. In addition, defrecord will define type-and-value-based =, and will defined Java .hashCode and .equals consistent with the contract for java.util.Map. When AOT compiling, generates compiled bytecode for a class with the given name (a symbol), prepends the current ns as the package, and writes the .class file to the *compile-path* directory. Two constructors will be defined, one taking the designated fields followed by a metadata map (nil for none) and an extension field map (nil for none), and one taking only the fields (using nil for meta and extension fields). Note that the field names __meta, __extmap, __hash and __hasheq are currently reserved and should not be used when defining your own records. Given (defrecord TypeName ...), two factory functions will be defined: ->TypeName, taking positional parameters for the fields, and map->TypeName, taking a map of keywords to field values.
;; from Stu's examples:
(defrecord Person [fname lname address])
-> user.Person
(defrecord Address [street city state zip])
-> user.Address
(def stu (Person. "Stu" "Halloway"
(Address. "200 N Mangum"
"Durham"
"NC"
27701)))
-> #'user/stu
(:lname stu)
-> "Halloway"
(-> stu :address :city)
-> "Durham"
(assoc stu :fname "Stuart")
-> #:user.Person{:fname "Stuart", :lname "Halloway", :address #:user.Address{:street "200 N Mangum", :city "Durham", :state "NC", :zip 27701}}
(update-in stu [:address :zip] inc)
-> #:user.Person{:fname "Stu", :lname "Halloway", :address #:user.Address{:street "200 N Mangum", :city "Durham", :state "NC", :zip 27702}}
;; This example shows how to implement a Java interface in defrecord.
;; We'll implement FileNameMap (because it has a simple interface,
;; not for its real purpose).
(import java.net.FileNameMap)
-> java.net.FileNameMap
;; Define a record named Thing with a single field a. Implement
;; FileNameMap interface and provide an implementation for the single
;; method: String getContentTypeFor(String fileName)
(defrecord Thing [a]
FileNameMap
(getContentTypeFor [this fileName] (str a "-" fileName)))
-> user.Thing
;; construct an instance of the record
(def thing (Thing. "foo"))
-> #'user/thing
;; check that the instance implements the interface
(instance? FileNameMap thing)
-> true
;; get all the interfaces for the record type
(map #(println %) (.getInterfaces Thing))
-> java.net.FileNameMap
-> clojure.lang.IObj
-> clojure.lang.ILookup
-> clojure.lang.IKeywordLookup
-> clojure.lang.IPersistentMap
-> java.util.Map
-> java.io.Serializable
;; actually call the method on the thing instance and pass "bar"
(.getContentTypeFor thing "bar")
-> "foo-bar"
;; prepare a protocol
user=> (defprotocol Fun-Time (drinky-drinky [_]))
Fun-Time
;; define a record and extend the previous protocol, implementing its function
user=> (defrecord Someone [nick-name preffered-drink] Fun-Time (drinky-drinky [_] (str nick-name "(having " preffered-drink "): uuumm")))
user.Someone
;; NOTE how 'nick-name' and 'preffered-drink' are symbols that are not declared anywhere, they are 'provided' inside the function
;; instantiate the protocol once and store it
user=> (def dude (->Someone "belun" "daiquiri"))
#'user/dude
;; use the function defined inside the protocol on the protocol instance
user=> (drinky-drinky dude)
"belun(having daiquiri): uuumm"
;; courtesy of Howard Lewis Ship - http://java.dzone.com/articles/changes-cascade-and-cautionary
; If you define a defrecord in one namespace and want to use it
; from another, there are 2 options:
; 1. use the constructor (->Record)
; (only available in clojure >= 1.4)
;
; 2. first require the namespace and then import
; the record as a regular class.
; The require+import order makes sense if you consider that first
; the namespace has to be compiled--which generates a class for
; the record--and then the generated class must be imported.
; (Thanks to raek in #clojure for the explanations!)
; Namespace "my/data.clj", where a defrecord is declared
(ns my.data)
(defrecord Employee [name surname])
; Option 1:
; Namescape "my/queries-option-one.clj", where a defrecord is used
(ns my-queries-one
(:use [my.data]))
(println
"Employees named Albert:"
(filter #(= "Albert" (.name %))
[(->Employee "Albert" "Smith")
(->Employee "John" "Maynard")
(->Employee "Albert" "Cheng")]))
; Option 2:
; Namescape "my/queries-option-two.clj", where a defrecord is used
(ns my.queries-option-two
(:require my.data)
(:import [my.data Employee]))
(println
"Employees named Albert:"
(filter #(= "Albert" (.name %))
[(Employee. "Albert" "Smith")
(Employee. "John" "Maynard")
(Employee. "Albert" "Cheng")]))
;; The map->Recordclass form works only in Clojure 1.3 or higher
(defrecord Foo [a b])
(defrecord Bar [a b c])
(defrecord Baz [a c])
(def f (Foo. 10 20))
(println f)
-> #user.Foo{:a 10, :b 20}
(def r (map->Bar (merge f {:c 30})))
(println r)
-> #user.Bar{:a 10, :b 20, :c 30}
(def z (map->Baz (merge f {:c 30})))
(println z)
-> #user.Baz{:a 10, :c 30, :b 20}
;; Construct a record from a vector.
(def info ["Carl" 20])
(defrecord Person [name age])
(apply ->Person info)
-> #user.Person{:name "Carl", :age 20}
;; This is particularly useful for mapping parsed CSV files to records
(map #(apply ->Person %) parsed-csv-file)
;; Destructuring a record
(defrecord Cat [age weight])
(def Jasper (Cat. 2 10))
(defn about-cat [{:keys [age weight]}]
(str age " yrs old and " weight " lbs"))
(about-cat Jasper)
->"2 yrs old and 10 lbs"
(defrecord Person [name age])
;;=> user.Person
(def ertu (->Person "Ertu" 24))
;;=> #'user/ertu
(record? ertu)
;;=> true
(record? (assoc ertu :address "Somewhere"))
;;=> true
;;removing base fields converts record to regular map
(record? (dissoc ertu :name))
;;=> false
;;iterate over the keys
;;define the record
(defrecord record-class [alplha beta])
;;create a defrecord variable
(def record (record-class. 1 2))
;;iterate over the keys
(for [key (keys record)]
(println (key record)))
;;define Address record
(defrecord Address [city state])
;;define Person record
(defrecord Person [firstname lastname ^Address address])
;;buid the constructor
(defn make-person ([fname lname city state]
(->Person fname lname (->Address city state))))
;;create a person
(def person1 (make-person "John" "Doe" "LA" "CA"))
;;retrieve values
(:firstname person1)
(:city (:address person1))