Skip to content

Commit 8a6c525

Browse files
committed
Added uniform metadata handling for atoms/refs/agents/vars/namespaces
Note - breaking change for agent/ref when supplying validator - validator must be passed using :validator option Added :validator and :meta options to agent/ref/atom Added alter-meta! and reset-meta! for reference types renamed set-validator to set-validator! Validators now can simply return false, or throw Refactoring, added IMeta, IReference Switched to longs for Ref ids
1 parent 96757ae commit 8a6c525

File tree

17 files changed

+398
-188
lines changed

17 files changed

+398
-188
lines changed

src/clj/clojure/core.clj

Lines changed: 86 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@
150150
#^{:arglists '([obj])
151151
:doc "Returns the metadata of obj, returns nil if there is no metadata."}
152152
meta (fn meta [x]
153-
(if (instance? clojure.lang.IObj x)
154-
(. #^clojure.lang.IObj x (meta)))))
153+
(if (instance? clojure.lang.IMeta x)
154+
(. #^clojure.lang.IMeta x (meta)))))
155155

156156
(def
157157
#^{:arglists '([#^clojure.lang.IObj obj m])
@@ -1047,14 +1047,31 @@
10471047
[sym] (. clojure.lang.Var (find sym)))
10481048

10491049
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Refs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1050+
(defn #^{:private true}
1051+
setup-reference [r options]
1052+
(let [opts (apply hash-map options)]
1053+
(when (:meta opts)
1054+
(.resetMeta r (:meta opts)))
1055+
(when (:validator opts)
1056+
(.setValidator r (:validator opts)))
1057+
r))
1058+
10501059
(defn agent
1051-
"Creates and returns an agent with an initial value of state and an
1052-
optional validate fn. validate-fn must be nil or a side-effect-free fn of
1053-
one argument, which will be passed the intended new state on any state
1060+
"Creates and returns an agent with an initial value of state and
1061+
zero or more options (in any order):
1062+
1063+
:meta metadata-map
1064+
1065+
:validator validate-fn
1066+
1067+
If metadata-map is supplied, it will be come the metadata on the
1068+
agent. validate-fn must be nil or a side-effect-free fn of one
1069+
argument, which will be passed the intended new state on any state
10541070
change. If the new state is unacceptable, the validate-fn should
1055-
throw an exception."
1071+
return false or throw an exception."
10561072
([state] (new clojure.lang.Agent state))
1057-
([state validate-fn] (new clojure.lang.Agent state validate-fn)))
1073+
([state & options]
1074+
(setup-reference (agent state) options)))
10581075

10591076
(defn send
10601077
"Dispatch an action to an agent. Returns the agent immediately.
@@ -1119,14 +1136,21 @@
11191136
[] (. clojure.lang.Agent shutdown))
11201137

11211138
(defn ref
1122-
"Creates and returns a Ref with an initial value of x and an optional validate fn.
1123-
validate-fn must be nil or a side-effect-free fn of one argument, which will
1124-
be passed the intended new state on any state change. If the new
1125-
state is unacceptable, the validate-fn should throw an
1126-
exception. validate-fn will be called on transaction commit, when
1127-
all refs have their final values."
1139+
"Creates and returns a Ref with an initial value of x and zero or
1140+
more options (in any order):
1141+
1142+
:meta metadata-map
1143+
1144+
:validator validate-fn
1145+
1146+
If metadata-map is supplied, it will be come the metadata on the
1147+
ref. validate-fn must be nil or a side-effect-free fn of one
1148+
argument, which will be passed the intended new state on any state
1149+
change. If the new state is unacceptable, the validate-fn should
1150+
return false or throw an exception. validate-fn will be called on
1151+
transaction commit, when all refs have their final values."
11281152
([x] (new clojure.lang.Ref x))
1129-
([x validate-fn] (new clojure.lang.Ref x validate-fn)))
1153+
([x & options] (setup-reference (ref x) options)))
11301154

11311155
(defn deref
11321156
"Also reader macro: @ref/@agent/@var/@atom Within a transaction,
@@ -1135,11 +1159,45 @@
11351159
or atom, returns its current state."
11361160
[#^clojure.lang.IRef ref] (. ref (get)))
11371161

1138-
(defn set-validator
1162+
(defn atom
1163+
"Creates and returns an Atom with an initial value of x and zero or
1164+
more options (in any order):
1165+
1166+
:meta metadata-map
1167+
1168+
:validator validate-fn
1169+
1170+
If metadata-map is supplied, it will be come the metadata on the
1171+
atom. validate-fn must be nil or a side-effect-free fn of one
1172+
argument, which will be passed the intended new state on any state
1173+
change. If the new state is unacceptable, the validate-fn should
1174+
return false or throw an exception."
1175+
([x] (new clojure.lang.Atom x))
1176+
([x & options] (setup-reference (atom x) options)))
1177+
1178+
(defn swap!
1179+
"Atomically swaps the value of atom to be:
1180+
(apply f current-value-of-atom args). Note that f may be called
1181+
multiple times, and thus should be free of side effects. Returns
1182+
the value that was swapped in."
1183+
[#^clojure.lang.Atom atom f & args] (.swap atom f args))
1184+
1185+
(defn compare-and-set!
1186+
"Atomically sets the value of atom to newval if and only if the
1187+
current value of the atom is identical to oldval. Returns true if
1188+
set happened, else false"
1189+
[#^clojure.lang.Atom atom oldval newval] (.compareAndSet atom oldval newval))
1190+
1191+
(defn reset!
1192+
"Sets the value of atom to newval without regard for the
1193+
current value. Returns newval."
1194+
[#^clojure.lang.Atom atom newval] (.reset atom newval))
1195+
1196+
(defn set-validator!
11391197
"Sets the validator-fn for a var/ref/agent/atom. validator-fn must be nil or a
11401198
side-effect-free fn of one argument, which will be passed the intended
11411199
new state on any state change. If the new state is unacceptable, the
1142-
validator-fn should throw an exception. If the current state (root
1200+
validator-fn should return false or throw an exception. If the current state (root
11431201
value if var) is not acceptable to the new validator, an exception
11441202
will be thrown and the validator will not be changed."
11451203
[#^clojure.lang.IRef iref validator-fn] (. iref (setValidator validator-fn)))
@@ -1148,6 +1206,18 @@
11481206
"Gets the validator-fn for a var/ref/agent/atom."
11491207
[#^clojure.lang.IRef iref] (. iref (getValidator)))
11501208

1209+
(defn alter-meta!
1210+
"Atomically sets the metadata for a namespace/var/ref/agent/atom to be:
1211+
1212+
(apply f its-current-meta args)
1213+
1214+
f must be free of side-effects"
1215+
[#^clojure.lang.IReference iref f & args] (.alterMeta iref f args))
1216+
1217+
(defn reset-meta!
1218+
"Atomically resets the metadata for a namespace/var/ref/agent/atom"
1219+
[#^clojure.lang.IReference iref metadata-map] (.resetMeta iref metadata-map))
1220+
11511221
(defn commute
11521222
"Must be called in a transaction. Sets the in-transaction-value of
11531223
ref to:

src/clj/clojure/core_proxy.clj

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -339,57 +339,5 @@
339339
(lazy-cons (new clojure.lang.MapEntry (first pseq) (v (first pseq)))
340340
(thisfn (rest pseq))))) (keys pmap))))))
341341

342-
(import '(java.util.concurrent.atomic AtomicReference))
343342

344-
(defn atom
345-
"Creates and returns a new Atom with an initial value of x and an
346-
optional validate fn. validate-fn must be nil or a side-effect-free
347-
fn of one argument, which will be passed the intended new state on
348-
any state change. If the new state is unacceptable, the validate-fn
349-
should throw an exception."
350-
([x] (atom x nil))
351-
([x validator-fn]
352-
(let [validator (AtomicReference. nil)
353-
atom (proxy [AtomicReference clojure.lang.IRef] [x]
354-
(getValidator [] (.get validator))
355-
(setValidator [f]
356-
(when f
357-
(try
358-
(f @this)
359-
(catch Exception e
360-
(throw (IllegalStateException. "Invalid atom state" e)))))
361-
(.set validator f)))]
362-
(set-validator atom validator-fn)
363-
atom)))
364-
365-
(defn swap!
366-
"Atomically swaps the value of atom to be:
367-
(apply f current-value-of-atom args). Note that f may be called
368-
multiple times, and thus should be free of side effects. Returns
369-
the value that was swapped in."
370-
[#^AtomicReference atom f & args]
371-
(let [validate (get-validator atom)]
372-
(loop [oldv (.get atom)]
373-
(let [newv (apply f oldv args)]
374-
(when validate
375-
(try
376-
(validate newv)
377-
(catch Exception e
378-
(throw (IllegalStateException. "Invalid atom state" e)))))
379-
(if (.compareAndSet atom oldv newv)
380-
newv
381-
(recur (.get atom)))))))
382-
383-
(defn compare-and-set!
384-
"Atomically sets the value of atom to newval if and only if the
385-
current value of the atom is identical to oldval. Returns true if
386-
set happened, else false"
387-
[#^AtomicReference atom oldval newval]
388-
(let [validate (get-validator atom)]
389-
(when validate
390-
(try
391-
(validate newval)
392-
(catch Exception e
393-
(throw (IllegalStateException. "Invalid atom state" e)))))
394-
(.compareAndSet atom oldval newval)))
395343

src/jvm/clojure/lang/ARef.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/**
2+
* Copyright (c) Rich Hickey. All rights reserved.
3+
* The use and distribution terms for this software are covered by the
4+
* Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
5+
* which can be found in the file CPL.TXT at the root of this distribution.
6+
* By using this software in any fashion, you are agreeing to be bound by
7+
* the terms of this license.
8+
* You must not remove this notice, or any other, from this software.
9+
**/
10+
11+
/* rich Jan 1, 2009 */
12+
13+
package clojure.lang;
14+
15+
public abstract class ARef extends AReference implements IRef {
16+
private volatile IFn validator = null;
17+
18+
public ARef() {
19+
super();
20+
}
21+
22+
public ARef(IPersistentMap meta) {
23+
super(meta);
24+
}
25+
26+
void validate(IFn vf, Object val){
27+
try{
28+
if(vf != null && !RT.booleanCast(vf.invoke(val)))
29+
throw new IllegalStateException("Invalid reference state");
30+
}
31+
catch(RuntimeException re)
32+
{
33+
throw re;
34+
}
35+
catch(Exception e)
36+
{
37+
throw new IllegalStateException("Invalid reference state", e);
38+
}
39+
}
40+
41+
void validate(Object val){
42+
validate(validator,val);
43+
}
44+
45+
public void setValidator(IFn vf){
46+
try
47+
{
48+
validate(vf,get());
49+
}
50+
catch (Exception e)
51+
{
52+
throw new RuntimeException(e);
53+
}
54+
validator = vf;
55+
}
56+
57+
public IFn getValidator(){
58+
return validator;
59+
}
60+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Copyright (c) Rich Hickey. All rights reserved.
3+
* The use and distribution terms for this software are covered by the
4+
* Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
5+
* which can be found in the file CPL.TXT at the root of this distribution.
6+
* By using this software in any fashion, you are agreeing to be bound by
7+
* the terms of this license.
8+
* You must not remove this notice, or any other, from this software.
9+
**/
10+
11+
/* rich Dec 31, 2008 */
12+
13+
package clojure.lang;
14+
15+
public class AReference implements IReference {
16+
private IPersistentMap _meta;
17+
18+
public AReference() {
19+
this(null);
20+
}
21+
22+
public AReference(IPersistentMap meta) {
23+
_meta = meta;
24+
}
25+
26+
synchronized public IPersistentMap meta() {
27+
return _meta;
28+
}
29+
30+
synchronized public IPersistentMap alterMeta(IFn alter, ISeq args) throws Exception {
31+
_meta = (IPersistentMap) alter.applyTo(new Cons(_meta, args));
32+
return _meta;
33+
}
34+
35+
synchronized public IPersistentMap resetMeta(IPersistentMap m) {
36+
_meta = m;
37+
return m;
38+
}
39+
40+
}

src/jvm/clojure/lang/Agent.java

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@
1616
import java.util.concurrent.atomic.AtomicReference;
1717
import java.util.Map;
1818

19-
public class Agent implements IRef{
19+
public class Agent extends ARef {
2020
volatile Object state;
21-
volatile IFn validator = null;
22-
AtomicReference<IPersistentStack> q = new AtomicReference(PersistentQueue.EMPTY);
21+
AtomicReference<IPersistentStack> q = new AtomicReference(PersistentQueue.EMPTY);
2322
AtomicReference<IPersistentMap> watchers = new AtomicReference(PersistentHashMap.EMPTY);
2423

2524
volatile ISeq errors = null;
@@ -116,29 +115,18 @@ public Agent(Object state) throws Exception{
116115
this(state,null);
117116
}
118117

119-
public Agent(Object state, IFn validator) throws Exception{
120-
this.validator = validator;
121-
setState(state);
118+
public Agent(Object state, IPersistentMap meta) throws Exception {
119+
super(meta);
120+
setState(state);
122121
}
123122

124123
boolean setState(Object newState) throws Exception{
125-
validate(getValidator(),newState);
124+
validate(newState);
126125
boolean ret = state != newState;
127126
state = newState;
128127
return ret;
129128
}
130129

131-
void validate(IFn vf, Object val){
132-
try{
133-
if(vf != null)
134-
vf.invoke(val);
135-
}
136-
catch(Exception e)
137-
{
138-
throw new IllegalStateException("Invalid agent state", e);
139-
}
140-
}
141-
142130
public Object get() throws Exception{
143131
if(errors != null)
144132
{
@@ -147,16 +135,7 @@ public Object get() throws Exception{
147135
return state;
148136
}
149137

150-
public void setValidator(IFn vf){
151-
validate(vf,state);
152-
validator = vf;
153-
}
154-
155-
public IFn getValidator(){
156-
return validator;
157-
}
158-
159-
public ISeq getErrors(){
138+
public ISeq getErrors(){
160139
return errors;
161140
}
162141

0 commit comments

Comments
 (0)