language-ext v3.2.6 Release Notes
Release Date: 2019-07-14 // almost 5 years ago-
🔒 One aspect of using immutable data-types like
Map
,Seq
,HashSet
, etc. is that in most applications, at some point, you're likely to have some shared reference to one and need to mutate that shared reference. This often requires using synchronisation primitives likelock
(which are not composable and are prone to error).Atom
With a nod to the
atom
type in Clojure language-ext now has two new types:Atom<A>
Atom<M, A>
These types all wrap a value of
A
and provides a method:Swap
(and Prelude functionswap
) for atomically mutating the wrapped value without locking.var atom = Atom(Set("A", "B", "C"));atom.Swap(old =\> old.Add("D"));atom.Swap(old =\> old.Add("E"));atom.Swap(old =\> old.Add("F"));Debug.Assert(atom == Set("A", "B", "C", "D", "E", "F"));
⚡️ Atomic update
⚡️ One thing that must be noted is that if another thread mutates the atom whilst you're running
Swap
then your update will rollback.Swap
will then re-run the provided lambda function with the newly updated value (from the other thread) so that it can get a valid updated value to apply. This means that you must be careful to not have side-effects in theSwap
function, or at the very least it needs to be reliably repeatable.Validation
⚡️ The
Atom
andAtomRef
constructors can take aFunc<A, bool>
validation lambda. This is run against the initial value and all subsequent values before being swapped. If the validation lambda returnsfalse
for the proposed value thenfalse
is returned fromSwap
and no update takes place.Events
The
Atom
andAtomRef
types all have aChange
event:public event AtomChangedEvent\<A\> Change;
⚡️ It is fired after each successful atomic update to the wrapped value. If you're using the
LanguageExt.Rx
extensions then you can also consume theatom.OnChange()
observable.📇 Metadata
The two types, with an
M
generic argument, take an additional meta-data argument on construction which can be used to pass through an environment or some sort of context for theSwap
functions:var env = new Env();var atom = Atom(env, Set("A", "B", "C"));atom.Swap((nenv, old) =\> old.Add("D"));
➕ Additional arguments
There are also other variants of
Swap
that can take up to two additional arguments and pass them through to the lambda:var atom = Atom(Set(1, 2, 3));atom.Swap(4, 5, (x, y, old) =\> old.Add(x).Add(y));Debug.Assert(atom == Set(1, 2, 3, 4, 5));
Accessing the value
The wrapped value can be accessed by calling
atom.Value
or using theimplicit operator
conversion toA
.Pool
🐎 The updates to the object pooling system also improves the performance of the
Lst
,Set
, andMap
enumerators.Conclusion
I hope the
Atom
types finds some use, I know I've bumped up against this issue many times in the past and have either ended up manually building synchronisation primitives or fallen back to using the uglyConcurrentDictionary
or similar. I will perhaps take a look at theref
system in Clojure too - which is a mechanism for atomic updates of multiple items (STM essentially).