Reputation: 3623
Suppose you have two values x
and y
such that y
should be computed every time x
is updated. In Java, everything is an object so I have little choice. The way to do it would be via a class, e.g.
public Data {
private type x, y;
Data(type x) {
this.x = x;
this.y = null;
}
void updateX(type2 val) {
// perform operation to update X
// perform (expensive) operation to update Y
}
void getX() {...}
void getY() {...}
}
In clojure, a direct translation of this might use deftype
and defprotocol
/definterface
. But that's too much for just defining a dependency between two variables. In particular, I have an objection to giving it a name and treating it on par with actual types and protocols.
I know this is what defrecord
s are for -- when you need to make a class that does not represent an entity in the business domain, but then defrecord
s are immutable.
Do I have other choices? I had a look at RxClojure
because the situation here seems to be "reactive", but that seems to be oriented toward "emit"-ing events rather than e.g. just storing the latest one in an atom.
Upvotes: 1
Views: 97
Reputation: 17859
you could also use add-watch facility for that. For example:
(defn entangle [x-val computation]
(let [x (atom x-val)
y (atom (computation x-val))]
(add-watch x nil (fn [_ _ old-x new-x]
(reset! y (computation new-x))))
{:x x
:y y}))
In the above example y
value depends on x
value through the computation
function.
(let [{:keys [x y]} (entangle 10 #(* % %))]
(println "x:" @x "y:" @y)
(swap! x inc)
(println "x:" @x "y:" @y)
(reset! x 200)
(println "x:" @x "y:" @y))
;; x: 10 y: 100
;; x: 11 y: 121
;; x: 200 y: 40000
nil
This could be handy in some situations, though generally such tangled code eliminates referential transparency, and i would probably avoid using it.
Upvotes: 1
Reputation: 21984
I can't help but think that you're overthinking this.
(def some-var (atom some-value))
(def derived (atom (some-expensive-fn some-var)))
(defn update-derived
[old-state new-state]
(new-state)
(defn update-var
[x]
(swap! derived
update-derived
(swap! some-var (fn [old-state] (x)))))
We define some state, some derived state, and a function that takes a new value and updates both.
If you want to be able to do this multiple times you can use a factory that returns a closure:
(defn make-stateful-thing-with-derived-value
[init-x-value compute-y]
(let [x (atom init-x-value)
y (atom (compute-y init-x-value))]
(list x
y
(fn [new-x-value]
(swap! y
(fn [old-s] (compute-y new-x-value))
(swap! x (fn [old-s] (new-x-value)))))))
The function takes an initial x value and a function to derive y and returns a list with the atoms x and y and a function that accepts a value to update them.
Upvotes: 1