A library with various basic utilities for programming with Clojure.
0.28.0define-record-type has changed from
active.clojure.record to active.clojure.cljs.record.active-clojure version gets picked up by
Leiningen, you should exclude previous active-clojure that are included in
the dependencies transitively by adding :exclusions [active-clojure] to
libraries that come with the dependency. When in doubt, check lein deps :why active-clojure.The active.clojure.record namespace implements a
define-record-type form similar to Scheme's SRFI
9.
Example: A card consists of a number and a color
(ns namespace
(:require [active.clojure.record :as r]))
(r/define-record-type Card
(make-card number color)
card?
[number card-number
color card-color])
;; Creating a record with field values 3 and "hearts"
(def card-1 (make-card 3 "hearts"))
;; Get number of this card via selector
(card-number card-1)
;; => 3
;; Predicate test
(card? card-1)
;; => true
(card? "3 of Hearts")
;; => false
You can provide additional options in an option-map as second argument to define-record-type.
By providing a value to the option key :spec, a spec for the record type is created.
The fields of records can also be "spec'd" via meta information.
(spec/def ::color #{:diamonds :hearts :spades :clubs})
(defn is-valid-card-number?
[n]
(and (int? n)
(> n 0) (< n 14)))
(r/define-record-type Card
{:spec ::card}
(make-card number color)
card?
[^{:spec is-valid-card-number?} number card-number
^{:spec ::color} color card-color])
(spec/valid? ::card (make-card 5 :hearts))
;; => true
(spec/valid? ::card (make-card 5 "hearts"))
;; => false
(spec/explain ::card (make-card 5 "hearts"))
;; => val: #namespace.Card{:number 124, :color "hearts"} fails spec: :namespace/card
;; predicate: (valid? :namespace/color (card-color %))
To use spec/def, spec/valid?, and spec/explain you have to require clojure.spec.alpha
in your ns form.
You also get a spec for the constructor function. If instrumentation is enabled
(via clojure.spec.test.alpha/instrument), the constructor is checked using the specs
provided for the selector functions:
;; Does not get checked without instrument.
(make-card 20 :hearts)
;; => #namespace.Card{:number 20 :color :hearts}
;; Now, with instrumentation.
(clojure.spec.test.alpha/instrument)
(make-card 20 :hearts)
;; => Spec assertion failed.
;;
;; Spec: #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x31346221
;; "clojure.spec.alpha$regex_spec_impl$reify__2436@31346221"]
;; Value: (20 :hearts)
;;
;; Problems:
;;
;; val: 20
;; in: [0]
;; failed: is-valid-card-number?
;; at: [:args :number]
If you provide a value (uid) to the nongenerative option,
the record-creation operation is nongenerative i.e.,
a new record type is created only if no previous call to
define-record-type was made with the uid.
Otherwise, an error is thrown.
If uid is true, a uuid is created automatically.
If this option is not given (or value is falsy),
the record-creation operation is generative, i.e.,
a new record type is created even if a previous call
to define-record-type was made with the same arguments.
Default is true.
If you provide the key:val pair :arrow-constructor?:false,
the creation of the arrow-constructor of the defrecord call is omitted,
i.e.
(define-record-type Test {:arrow-constructor? false} (make-test a) ...)
won't yield a function ->Test.
Default is true.
If you don't want your records to implement the Map-protocols (in Clojure
these are java.util.Map and clojure.lang.IPersistentMap, in ClojureScript
IMap and IAssociative), you can provide the key:val pair
:map-protocol?:false to the options map.
There are a number of interfaces, that our records defaultly implement (like
e.g. aforementioned java.util.Map). Providing key:val pair
:remove-interfaces:[interface1 interface2 ...] will prevent the
implementations of the given interfaces.
You can implement protocols and interfaces with the
define-record-type-statement:
(defprotocol SaySomething
(say [this]))
(r/define-record-type Card
(make-card number color)
card?
[number card-number
color card-color]
SaySomething
(say [this] (str "The card's color is " (card-color this))))
(say (make-card 3 :hearts))
You can also override the defaultly implemented interfaces/protocols by the same means. You don't have to provide every method of a default interface, those left out by you will remain the default ones.
Default is true.
If you provide the key:val pair :java-class?:false, no java class is created
for the given type, and instead a record-type-descriptor is created.
If you provide the key:val pair :rtd-record?:true, an own record
implementation for ClojureScript is used instead of defrecord.
You can provide meta data via (define-record-type ^{:foo "bar"} MyRecord). This meta data is then "inherited" to all created symobls
(like ->MyRecord).
If you use an RTD record (:java-class?, :rtd-record? options), this data
is retrievable via (MyRecord :meta).
The active.clojure.lens namespace implements lenses. Lenses
provide a subtle way to access and update the elements of a structure
and are well-known in functional programming
languages.
If you want to update only one field in a record, it is cumbersome to write out the whole make-constructor expression:
(r/define-record-type Person
make-person
person?
[name person-name
age person-age
address person-address
job person-job])
(def mustermann (make-person "Max Mustermann" 35 "Hechinger Straße 12/1, 72072 Tübingen"
"Software Architect"))
(make-person "Max Maier"
(person-age mustermann)
(person-address mustermann)
(person-job mustermann))
With lenses you can set and update fields easily:
(lens/shove mustermann
person-name
"Max Maier")
(lens/overhaul mustermann
person-age
inc)
Note: The lens functions don't alter the given record but create and return
a new one.
You can even combine lenses to update records inside records:
(r/define-record-type Address
make-address
adress?
[street address-street
number address-number
city address-city
postalcode address-postalcode])
(def mustermann (make-person "Max Mustermann" 35
(make-address "Hechinger Strasse" "12/1"
"Tübingen" 72072)
"Software Architect"))
(lens/shove mustermann
(lens/>> person-address address-street)
"Hechinger Straße")
The active.clojure.condition namespace implements conditions,
specifically crafted exception objects that form a protocol for
communicating the cause of an exception, similar to the condition
system in R6RS Scheme.
The active.clojure.config namespace implements application
configuration via a big map.
The active.clojure.debug namespace implements some useful debugging
tools such as a macro pret that prints and returns its argument.
The active.clojure.match namespace provides some syntactic sugar
for map matching around core.match.
The active.clojure.functions namespace provides the same higher order
functions that clojure.core does, but implemented via records and
IFn, so that the returned "functions" are = if created with = arguments.
These can be very handy for using React-based libraries like Reacl, which can optimize work based on the equality of values.
An example usage of the active.clojure.monad namespace can be found at https://github.com/active-group/active-clojure-monad-example
The Clojure tests can be executed via
lein test
For auto-testing the ClojureScript code, we use figwheel-main. In a terminal, do
lein fig
which starts a CLJS REPL. Opening
http://localhost:9500/figwheel-extra-main/auto-testing
in a browser window will then run the tests and display the results. After every code change, it will automatically reload and re-run the tests, notifying you via the browser of the result.
Copyright © 2014-2019 Active Group GmbH
Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.
Can you improve this documentation? These fine people already did:
Kaan Sahin, Marcus Crestani, Simon Härer, Johannes Maier, David Frese, Mike Sperber & Marco SchneiderEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |