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
Copy file name to clipboardExpand all lines: docs/concepts.rst
+174-3Lines changed: 174 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -346,9 +346,76 @@ Control Structures
346
346
Basilisp features many variations on traditional programming control structures such as ``if`` and ``while`` loops thanks to the magic of :ref:`macros`.
347
347
Using these control structure variants in preference to raw :lpy:form:`if` s can often help clarify the meaning of your code while also using reducing the amount of code you have to write.
348
348
349
-
In addition to the stalwart :lpy:fn:`condp`, :lpy:fn:`and`, and :lpy:fn:`or`, Basilisp also features threading macros which help writing clear and concise code.
349
+
Of particular note are the ``when`` variants, which may be useful when you are only checking for a single condition:
350
+
351
+
.. code-block:: clojure
352
+
353
+
(when (some neg? coll)
354
+
(throw (ex-info "Negative values are not permitted" {:values coll})))
355
+
356
+
Users may also find the ``let`` variants of ``if`` and ``when`` particularly useful for binding a name for use conditionally:
357
+
358
+
.. code-block::
359
+
360
+
;; note that the return value from `re-matches` will not be bound if the return
361
+
;; value is `nil` or `false`, so we can safely destructure the return here
362
+
(defn parse-num
363
+
[s]
364
+
(when-let [[_ num] (re-matches #"(\d+)" s)]
365
+
(int num)))
366
+
367
+
Basilisp also features threading macros which help writing clear and concise code.
350
368
Threading macros can help transform deeply nested expressions into a much more readable pipeline of expressions whose source order matches the execution order at runtime.
351
369
370
+
Threading macros come in three basic variants, each of which can be useful in different circumstances:
371
+
372
+
- ``->`` is called "thread-first"; successive values will be slotted in as the *first* argument in the next expression
373
+
- ``->>`` is called "thread-last"; successive values will be slotted in as the *last* argument in the next expression
374
+
- ``as->`` is called "thread-as"; allows users to select where in the subsequent expression the previous expression will be slotted
375
+
376
+
.. code-block::
377
+
378
+
;; without threading, successive updates or modifications to maps and other
379
+
;; persistent data structures would be quite challenging to read
380
+
(update (assoc user :most-recent-login (datetime.datetime/now)) :num-logins inc)
381
+
382
+
;; thread-first macro can help unnest the above logic and make clearer the
@@ -418,6 +485,7 @@ Like the built-in :lpy:fn:`map`, ``pmap`` executes the provided function across
418
485
Various Functions
419
486
^^^^^^^^^^^^^^^^^
420
487
488
+
- Functions used for printing: :lpy:fn:`pr`, :lpy:fn:`pr-str`, :lpy:fn:`prn`, :lpy:fn:`prn-str`, :lpy:fn:`print`, :lpy:fn:`print-str`, :lpy:fn:`println`, :lpy:fn:`println-str`, :lpy:fn:`printf`, :lpy:fn:`with-in-str`, :lpy:fn:`with-out-str`, :lpy:fn:`flush`, :lpy:fn:`newline`
421
489
- Functions for throwing and introspecting exceptions: :lpy:fn:`ex-info`, :lpy:fn:`ex-cause`, :lpy:fn:`ex-data`, :lpy:fn:`ex-message`, :lpy:ns:`basilisp.stacktrace`
422
490
- Functions for generating random data: :lpy:fn:`rand`, :lpy:fn:`rand-int`, :lpy:fn:`rand-nth`, :lpy:fn:`random-uuid`, :lpy:fn:`random-sample`, :lpy:fn:`shuffle`
423
491
- Functions which can be used to introspect the Python type hierarchy: :lpy:fn:`class`, :lpy:fn:`cast`, :lpy:fn:`bases`, :lpy:fn:`supers`, :lpy:fn:`subclasses`
@@ -846,11 +914,114 @@ The stored value can be modified using :lpy:fn:`vswap!` and :lpy:fn:`vreset!`.
846
914
Transducers
847
915
-----------
848
916
849
-
TBD
917
+
Transducers are a tool for structuring pipelines of transformations on sequences of data which have some key advantages over simply composing :ref:`Seq <working_with_seqs>` operations:
918
+
919
+
1. Transducers are often more efficient than their equivalent composed Seq operations since they do not create intermediate Seqs for each step in the pipeline.
920
+
2. Transducers are composable.
921
+
3. Transducers are reusable.
922
+
923
+
Many of the Seq library functions provide an arity for creating a transducer directly which mirrors the functionality of its classical Seq usage.
924
+
For example:
925
+
926
+
.. code-block::
927
+
928
+
(map :price) ;; returns a transducer which fetches the :price key from a map
929
+
(keep identity) ;; returns a transducer which returns only non-nil values
930
+
(filter pos?) ;; returns a transducer which filters only positive values
931
+
932
+
Each step above can be used as a transducer on its own, but one of the key benefits of transducers is composition.
933
+
Transducing functions can be combined using the standard :lpy:fn:`comp` function:
934
+
935
+
.. code-block::
936
+
937
+
(def xform
938
+
(comp
939
+
(map :price)
940
+
(keep identity)
941
+
(filter pos?)))
942
+
943
+
When combined using ``comp``, these transducers are run not in the classical order of function composition (from outside in) but rather in the order they appear in the source.
944
+
The transducer above is equivalent to writing the following in classical Seq library functions:
945
+
946
+
.. code-block::
947
+
948
+
(filter pos? (keep identity (map :price coll)))
949
+
950
+
;; or simplified using the ->> macro
951
+
(->> coll
952
+
(map :price)
953
+
(keep identity)
954
+
(filter pos?))
955
+
956
+
.. _applying_transducers:
957
+
958
+
Applying Transducers
959
+
^^^^^^^^^^^^^^^^^^^^
960
+
961
+
Once you've created a transducer function, you'll want to use it!
962
+
The Basilisp core library provides a number of different tools for applying transducers to sequence or collection.
963
+
964
+
Imagine we have an input dataset that looks like this with the given transducer:
For a non-caching lazy sequence, reach for :lpy:fn:`eduction`.
1001
+
For cases which you may only ever intend to iterate over a sequence once and do not need its results cached, this may be more efficient.
1002
+
1003
+
Finally, :lpy:fn:`sequence` creates a lazy sequence of applying the transducer functions to an input sequence.
1004
+
Note that although the input sequence is consumed lazily, each step in the transducer is run for every consumed element from the sequence.
1005
+
1006
+
.. _early_transducer_termination:
1007
+
1008
+
Early Termination
1009
+
^^^^^^^^^^^^^^^^^
1010
+
1011
+
Transducers (and reducers in general) can be terminated early by wrapping the return value in a call to :lpy:fn:`reduced` (or use the utility function :lpy:fn:`ensure-reduced` if to avoid double wrapping the final value).
1012
+
Transducers and :lpy:fn:`reduce` check for reduced values (as by :lpy:fn:`reduced?`) and return the wrapped value if one is encountered.
1013
+
1014
+
The :lpy:fn:`halt-when` transducer makes use of this pattern.
0 commit comments