defrecord

added
1.2

ns
clojure.core

type
macro

(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))