But you can use it in many other languages such as C#, Python, Lua and more.
I’ll leave the mistake for posterity… and to remind myself to check language features actually exist in the language I’m talking about.
That’s what happens when you switch languages all the time xD
The code for the language intro follows pretty much what we did but is more concise.
In this one you’ll need, before you run the code, to turn the console tab on and the output tab off, otherwise you won’t see the code execution results, since everything happens in the console.
Everyone has had to contend with handling a variable that is changed by different parts of a program at the same time.
Problems like Race Conditions and Deadlocks are handled using Locking, an error-prone technique, without digging deeper to reach the real culprit: mutable state.
Clojure doesn’t take as radical a stance as Erlang, which has solved the problem by enforcing single assignment, it instead uses Software Transactional Memory.
The Clojure Way
Software Transactional Memory
Clojure applies functional programming staples of immutable data structures and avoidance of side effects and for the remaining state it uses Software Transactional Memory, which in practice is similar to how a database controls concurrent access: changes of state are wrapped in transactions.
Atomicity: either all changes of a transaction are applied or none.
Consistency: only valid changes are committed.
Isolation: no transaction sees the effect of other transactions.
Durability: changes are persistent.
It provides a mechanism for managing references and updates across threads that is easier to use and less error-prone than lock-based concurrency.
Identity and state
Clojure has a unique philosophy about state, inspired by Alfred North Whitehead: it states that mutable state doesn’t actually exist, but it’s an illusion, in a similar way to how a sequence of still frames produces the illusion of movement if shown in a sequence fast enough to trick the eye.
So mutable state is actually a causally-linked sequence of immutable values and time is derived from the perception of these successions. We apply pure functions to immutable values to derive new immutable values, assigning identity to those sequences of values.
Value: an immutable magnitude, quantity, number, or composite of these.
Identity: a putative entity we associate with a series of causally related values (states) over time
State: value of an identity at a moment in time
Time: relative before/after ordering of causal values.
Applying a function that modifies a data structure will return a new data structure rather than modifying the old one.
Values are immutable, they never change.
Clojure has 4 reference types: var, atom, ref and agent which represent identities.
All references contain some value, that can only be changed using the appropriate functions, which are different for every type.
Dereferencing will return the state of a reference at the time deref was invoked, which doesn’t guarantee that it won’t be different at a later point.
Dereferencing doesn’t block, reads need not be coordinated, it’s just grabbing the latest value of an identity.
Vars support thread-local state.
Binding works on a per-thread basis by default. The first time a var is bound is called root binding and is seen by default by all threads, though they can only change it locally.
The background thread also prints the root value, even though it’s inside the context of the binding statement due to the fact that the spawned thread gets its own value of a-var (the root value) and doesn’t see the rebound a-var in the spawning thread.
Used to change shared state synchronously coordinating changes, multiple refs can be updated in one trasaction.
Any mutation must be wrapped in dosync and more than one can be executed in the same transaction.
Used to change indipendent state in an uncoordinated, atomic, synchronous manner. Changes are instantly visible from all threads.
State is mutated using a function, one of the benefits is that the operation can be retried safely.
They provide a thread-safe mechanism for asynchronous, uncoordinated updates.
Their state can be accessed from any thread.
Send and send-off can be used to mutate agents, the difference being that send-off uses a growing thread pool.