You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -275,29 +306,119 @@ Threads may await the results of the promise using a blocking :lpy:fn:`deref` ca
275
306
Atoms
276
307
-----
277
308
278
-
TBD
309
+
Atoms are mutable, thread-safe reference containers which are useful for storing state that may need to be accessed (and changed) by multiple threads.
310
+
New atoms can be created with a default value using :lpy:fn:`atom`.
311
+
The state can be mutated in a thread-safe way using :lpy:fn:`swap!` and :lpy:fn:`reset!` (among others) without needing to coordinate with other threads.
312
+
Read the value of the atom using :lpy:fn:`deref`.
313
+
314
+
.. code-block::
315
+
316
+
(def a (atom 0))
317
+
(swap! a inc) ;; => 1
318
+
@a ;; => 1
319
+
(swap! a #(+ 3 %)) ;; => 4
320
+
@a ;; => 4
321
+
(reset! a 0) ;; => 0
322
+
@a ;; => 0
323
+
324
+
Atoms are designed to contain one of Basilisp's immutable :ref:`data_structures`.
325
+
The ``swap!`` function in particular uses the :lpy:fn:`compare-and-set!` function to atomically swap in the results of applying the provided function to the existing value.
326
+
``swap!`` attempts to compare and set the value in a loop until it succeeds.
327
+
Since atoms may be accessed by multiple threads simultaneously, it is possible that the value of an atom has changed between when the state was polled and when the function finished computing its final result.
328
+
Update functions should therefore be free of side-effects since they may be called multiple times.
329
+
330
+
.. note::
331
+
332
+
Atoms implement :py:class:`basilisp.lang.interfaces.IRef` and :py:class:`basilisp.lang.interfaces.IReference` and therefore support validators, watchers, and mutable metadata.
Basilisp's built-in reference types :ref:`vars` and :ref:`atoms` include support for metadata, validation, and watchers.
288
344
289
-
TBD
345
+
Unlike :ref:`metadata` on data structures, reference type metadata is mutable.
346
+
The identity of a reference type is the container, rather than the contained value, so it makes sense that if the value of a container can change so can the metadata.
347
+
:ref:`Var metadata <var_metadata>` is typically set at compile-time by a combination of compiler provided metadata and user metadata (typically via :lpy:form:`def`).
348
+
On the other hand, :ref:`atom <atoms>` have no metadata by default.
349
+
Metadata can be mutated using :lpy:fn:`alter-meta!` and :lpy:fn:`reset-meta!`.
350
+
351
+
Both Vars and atoms support validation of their contained value at the time it is set using a validator function.
352
+
Validator functions are functions of one argument returning either a single boolean value (where ``false`` indicates the value is invalid) or throwing an exception upon failure.
353
+
The validator will be called with the new proposed value of a ref before that value is applied.
354
+
355
+
.. code-block::
356
+
357
+
(def a (atom 0))
358
+
(set-validator! a (fn [v] (= 0 (mod v 2))))
359
+
(swap! a inc) ;; => throws basilisp.lang.exception.ExceptionInfo: Invalid reference state {:data 1 :validator <...>}
360
+
(swap! a #(+ 2 %)) ;; => 2
361
+
362
+
Vars and atoms also feature support for watch functions which will be called on changes to the contained value.
363
+
Watch functions are functions of 4 arguments (watch key, reference value, old value, and new value).
364
+
Unlike validators, watches may not veto proposed changes to the contained value and any return value will be ignored.
365
+
A watch can be added to a reference using :lpy:fn:`add-watch` using a key and watches may be removed using :lpy:fn:`remove-watch` using the same key.
366
+
367
+
.. code-block::
368
+
369
+
(def a (atom 0))
370
+
(add-watch a :print (fn [_ r old new] (println r "changed from" old "to" new)))
371
+
(swap! a inc) ;; => prints "<basilisp.lang.atom.Atom object at 0x113b01070> changed from 0 to 1"
372
+
;; => 1
373
+
374
+
.. note::
375
+
376
+
Watch functions are called synchronously after a value change in an nondeterministic order.
377
+
378
+
.. warning::
379
+
380
+
By the time a watch function is called, it is possible that the contained value has changed again, so users should use the provided arguments for the new and old value rather than attempting to :lpy:fn:`deref` the ref.
Basilisp supports creating transient versions of most of its :ref:`persistent collections <data_structures>` using the :lpy:fn:`transient` function.
392
+
Transient versions of persistent data structures use local mutability to improve throughput for common data manipulation operations.
393
+
Because transients are mutable, they are intended to be used in local, single-threaded contexts where you may be constructing or modifying a collection.
394
+
395
+
Despite their mutability, the APIs for mutating transient collections are intentionally quite similar to that of standard persistent data structures.
396
+
Unlike classical data structure mutation APIs, you may not simply hang on to a single reference and issue repeated function calls or methods to that same data structure.
397
+
Instead, you use the transient-compatible variants of the existing persistent data structure functions (those ending with a ``!``) such as :lpy:fn:`assoc!`, :lpy:fn:`conj!`, etc.
398
+
As with the persistent data structures, you must use the return value from each of these functions as the input to subsequent operations.
399
+
400
+
Once you have completed modifying a transient, you should call :lpy:fn:`persistent!` to freeze the data structure back into its persistent variant.
401
+
After freezing a transient back into a persistent data structure, references to the transient are no longer guaranteed to be valid and may throw exceptions.
402
+
403
+
Many :lpy:ns:`basilisp.core` functions already use transients under the hood by default.
404
+
Take for example this definition of a function to merge an arbitrary number of maps (much like :lpy:fn:`merge`).
405
+
406
+
.. code-block::
407
+
408
+
(defn merge [& maps]
409
+
(when (some identity maps)
410
+
(persistent!
411
+
(reduce #(conj! %1 %2) (transient {}) maps))))
412
+
413
+
.. note::
414
+
415
+
You can create transient versions of maps, sets, and vectors.
416
+
Lists may not be made transient, since there would be no benefit.
417
+
418
+
.. warning::
419
+
420
+
Transient data structures are not thread-safe and must therefore not be modified by multiple threads at once.
421
+
It is the user's responsibility to ensure synchronization mutations to transients across threads.
301
422
302
423
.. seealso::
303
424
@@ -308,7 +429,13 @@ TBD
308
429
Volatiles
309
430
---------
310
431
311
-
TBD
432
+
Volatiles are mutable, *non-thread-safe* reference containers which are useful for storing state that is mutable and is only changed in a single thread.
433
+
Create a new volatile using :lpy:fn:`volatile!`.
434
+
The stored value can be modified using :lpy:fn:`vswap!` and :lpy:fn:`vreset!`.
435
+
436
+
.. note::
437
+
438
+
Volatiles are most frequently used for creating performant stateful :ref:`transducers`.
Copy file name to clipboardExpand all lines: docs/runtime.rst
+33-1Lines changed: 33 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -106,7 +106,7 @@ See the documentation for :lpy:fn:`require` for more details.
106
106
Vars
107
107
----
108
108
109
-
Vars are mutable :ref:`reference types <references_and_refs>` which hold a reference to something.
109
+
Vars are mutable :ref:`reference types <reference_types>` which hold a reference to something.
110
110
Users typically interact with Vars with the :lpy:form:`def` form and the :lpy:fn:`basilisp.core/defn` macro which create Vars to hold he result of the expression or function.
111
111
All values created with these forms are stored in Vars and interned in a Namespace so they can be looked up later.
112
112
The Basilisp compiler uses Vars interned in Namespaces during name resolution to determine if a name is referring to a local name (perhaps in a :lpy:form:`let` binding or as a function argument) or if it refers to a Var.
@@ -118,6 +118,38 @@ Specific metadata keys given during the creation of a Var can enable specific fe
Whenever a Var is defined as by :lpy:form:`def`, the compiler typically adds some metadata about where the Var was defined.
127
+
The following is a non-exhaustive list of potential metadata keys that may be set by the compiler.
128
+
129
+
All Vars might get the following keys:
130
+
131
+
- ``:ns`` the :ref:`namespace <namespaces>` the Var is interned in
132
+
- ``:name`` the name of the Var as a symbol
133
+
- ``:file`` the name of the source file where the Var was defined, or if it was defined in a REPL or via a string (such as by :lpy:fn:`eval`) then a descriptive string surrounded by ``<...>``
134
+
- ``:line``, ``:col``, ``:end-line``, ``:end-col`` location metadata about where in ``:file`` the Var was defined
135
+
- ``:doc`` the docstring provided at the time the Var was interned, if one
136
+
- ``:tag`` typically a return value for functions or type hint for values
137
+
138
+
Users may provide the following metadata which the compiler will pass through:
139
+
140
+
- ``:redef`` (see :ref:`compiler` for more details)
141
+
- ``:private`` (see :ref:`private_vars` below)
142
+
- ``:dynamic`` (see :ref:`dynamic_vars` below)
143
+
144
+
Vars containing functions (typically defined via some variant of :lpy:fn:`defn` or :lpy:fn:`defmacro`) might get the following keys:
145
+
146
+
- ``:macro`` if the function is a macro and eligible to be called during macroexpansion
147
+
- ``:arglists`` a sequence of the argument vectors for each defined arity of the function
0 commit comments