Skip to content

Commit e535e78

Browse files
authored
Concepts documentation module (#931)
More progress on #666
1 parent 5e77692 commit e535e78

File tree

6 files changed

+359
-38
lines changed

6 files changed

+359
-38
lines changed

docs/concepts.rst

Lines changed: 278 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,298 @@ Concepts
55

66
.. lpy:currentns:: basilisp.core
77
8+
This document outlines some of the key high-level concepts of Basilisp, most of which behave identically to their Clojure counterparts.
9+
The sections below tend to focus on how each of these concepts can be applied while using Basilisp.
10+
For those looking for more of a philosophical discussion of each of these concepts, you may find the corresponding `Clojure documentation <https://clojure.org/reference>`_ enlightening as it frequently emphasizes the *motivations* for the various concepts more heavily than this documentation.
11+
812
.. _data_structures:
913

1014
Data Structures
1115
---------------
1216

13-
TBD
17+
Basilisp provides a comprehensive set of immutable data structures and a set of scalar types inherited from the host Python environment.
18+
19+
.. _nil:
20+
21+
nil
22+
^^^
23+
24+
The value ``nil`` corresponds to Python's ``None``.
25+
Any type is potentially ``nil``, though many Basilisp functions are intended to handle ``nil`` values gracefully.
26+
Only the value ``nil`` and the :ref:`boolean <boolean_values>` value ``false`` are considered logical false in conditions.
27+
28+
.. seealso::
29+
30+
:lpy:fn:`nil?`
31+
32+
.. _boolean_values:
33+
34+
Boolean Values
35+
^^^^^^^^^^^^^^
36+
37+
The values ``true`` and ``false`` correspond to Python's ``True`` and ``False``, respectively.
38+
They are singleton instances of :external:py:class:`bool`
39+
Only the values ``nil`` and ``false`` are considered logical false in conditions.
40+
41+
.. seealso::
42+
43+
:lpy:fn:`false?`, :lpy:fn:`true?`
44+
45+
.. _numbers:
46+
47+
Numbers
48+
^^^^^^^
49+
50+
Basilisp exposes all of the built-in numeric types from Python and operations with those values match Python unless otherwise noted.
51+
52+
Integral values correspond to Python's arbitrary precision :external:py:class:`int` type.
53+
54+
Floating point values are represented by :external:py:class:`float`.
55+
For fixed-point arithmetic with user specified precision (corresponding with Clojure's ``BigDecimal`` type float suffixed with an ``M``), Basilisp uses Python's :external:py:class:`decimal.Decimal` class.
56+
57+
Complex numbers are backed by Python's :external:py:class:`complex`.
58+
59+
Ratios are represented by Python's :external:py:class:`fractions.Fraction` type.
60+
61+
.. seealso::
62+
63+
:ref:`arithmetic_division`
64+
65+
.. _strings_and_byte_strings:
66+
67+
Strings and Byte Strings
68+
^^^^^^^^^^^^^^^^^^^^^^^^
69+
70+
Basilisp's string type is Python's base :external:py:class:`str` type.
71+
Python's byte string type :external:py:class:`bytes` is also supported.
72+
73+
.. note::
74+
75+
Basilisp does not have a first class character type since there is no equivalent in Python.
76+
:ref:`reader_character_literals` can be read from source code, but will be converted into single-character strings.
77+
78+
.. seealso::
79+
80+
:lpy:ns:`basilisp.string` for an idiomatic string manipulation library
81+
82+
.. _keywords:
83+
84+
Keywords
85+
^^^^^^^^
86+
87+
Keywords are symbolic identifiers which always evaluate to themselves.
88+
Keywords consist of a name and an optional namespace, both of which are strings.
89+
The textual representation of a keyword includes a single leading ``:``, which is not part of the name or namespace.
90+
91+
Keywords are also functions of one or 2 arguments, roughly equivalent to calling :lpy:fn:`get` on a map or set with an optional default value argument.
92+
If the first argument is a :ref:`map <maps>`, then looks up the value associated with the keyword in the map.
93+
If the first argument is a :ref:`set <sets>`, then looks up if the keyword is a member of the set and returns itself if so.
94+
Returns the default value or ``nil`` (if no default value is specified) if either check fails.
95+
96+
.. code-block::
97+
98+
(def m {:kw 1 :other 2})
99+
(:kw m) ;; => 1
100+
(get m :kw) ;; => 1
101+
(:some-kw m) ;; => nil
102+
(:some-kw m 3) ;; => 3
103+
(get m :some-kw 3) ;; => 3
104+
105+
.. note::
106+
107+
Keyword values are interned and keywords are compared by identity, not by value.
108+
109+
.. warning::
110+
111+
Keywords can be created programmatically via :lpy:fn:`keyword` which may not be able to be read back by the :ref:`reader`, so use caution when creating keywords programmatically.
112+
113+
.. seealso::
114+
115+
:lpy:fn:`keyword`, :lpy:fn:`name`, :lpy:fn:`namespace`, :lpy:fn:`keyword?`
116+
117+
.. _symbols:
118+
119+
Symbols
120+
^^^^^^^
121+
122+
Symbols are symbolic identifiers which are typically used to refer to something else.
123+
Symbols consist of a name and an optional namespace, both strings.
124+
125+
Symbols, like :ref:`keywords`, can also be called like a function similar to :lpy:fn:`get` on a map or set with an optional default value argument.
126+
If the first argument is a :ref:`map <maps>`, then looks up the value associated with the symbol in the map.
127+
If the first argument is a :ref:`set <sets>`, then looks up if the symbol is a member of the set and returns itself if so.
128+
Returns the default value or ``nil`` (if no default value is specified) if either check fails.
129+
130+
.. code-block::
131+
132+
(def m {'sym 1 'other 2})
133+
('sym m) ;; => 1
134+
(get m 'sym) ;; => 1
135+
('some-sym m) ;; => nil
136+
('some-sym m 3) ;; => 3
137+
(get m 'sym 3) ;; => 3
138+
139+
.. note::
140+
141+
Basilisp will always try to resolve unquoted symbols, so be sure to wrap symbols in as ``(quote sym)`` or ``'sym`` if you just want a symbol.
142+
143+
.. warning::
144+
145+
Symbols can be created programmatically via :lpy:fn:`symbol` which may not be able to be read back by the :ref:`reader`, so use caution when creating symbols programmatically.
146+
147+
.. seealso::
148+
149+
:lpy:fn:`symbol`, :lpy:fn:`name`, :lpy:fn:`namespace`, :lpy:fn:`gensym`, :lpy:form:`quote`
150+
151+
.. _collection_types:
152+
153+
Collection Types
154+
^^^^^^^^^^^^^^^^
155+
156+
Basilisp includes the following data structures, all of which are both immutable and persistent.
157+
APIs which "modify" collections in fact produce new collections which may or may not share some structure with the original collection.
158+
As a result of their immutability, all of these collections are thread-safe.
159+
160+
Many of Basilisp's built-in collection types support creating :ref:`transient <transients>` versions of themselves for more efficient modification in a tight loop.
161+
162+
.. seealso::
163+
164+
:lpy:fn:`count`, :lpy:fn:`conj`, :lpy:fn:`seq`, :lpy:fn:`empty`, :lpy:fn:`not-empty`, :lpy:fn:`empty?`
165+
166+
.. _lists:
167+
168+
Lists
169+
#####
170+
171+
Lists are singly-linked lists.
172+
Unlike most other Basilisp collections, Lists directly implement :py:class:`basilisp.lang.interfaces.ISeq` (see :ref:`seqs`).
173+
You can get the count of a list in ``O(n)`` time via :lpy:fn:`count`.
174+
Items added via :lpy:fn:`conj` are added to the front of the list.
175+
176+
.. seealso::
177+
178+
:lpy:fn:`list`, :lpy:fn:`peek`, :lpy:fn:`pop`, :lpy:fn:`list?`
179+
180+
.. _queues:
181+
182+
Queues
183+
######
184+
185+
Queues are doubly-linked lists.
186+
You get the count of a queue in ``O(1)`` time via :lpy:fn:`count`.
187+
Items added via :lpy:fn:`conj` are added to the end of the queue.
188+
189+
.. seealso::
190+
191+
:lpy:fn:`queue`, :lpy:fn:`peek`, :lpy:fn:`pop`, :lpy:fn:`queue?`
192+
193+
.. _vectors:
194+
195+
Vectors
196+
#######
197+
198+
Vectors are sequential collections much more similar to Python lists or arrays in other languages.
199+
Vectors return their count in ``O(1)`` time via :lpy:fn:`count`.
200+
:lpy:fn:`conj` adds items to the end of a vector.
201+
Random access to vector elements by index (via :lpy:fn:`get` or :lpy:fn:`nth`) is ``O(log32(n))``.
202+
You can reverse a vector in constant time using :lpy:fn:`rseq`.
203+
204+
Vectors be called like a function similar to :lpy:fn:`nth` with an index and an optional default value, returning the value at the specified index if found.
205+
Returns the default value or ``nil`` (if no default value is specified) otherwise.
206+
207+
.. code-block::
208+
209+
(def v [:a :b :c])
210+
(v 0) ;; => :a
211+
(v 5) ;; => nil
212+
(v 5 :g) ;; => :g
213+
214+
.. seealso::
215+
216+
:lpy:fn:`vector`, :lpy:fn:`vec`, :lpy:fn:`get`, :lpy:fn:`nth`, :lpy:fn:`peek`, :lpy:fn:`pop`, :lpy:fn:`rseq`, :lpy:fn:`vector?`
217+
218+
.. _maps:
219+
220+
Maps
221+
####
222+
223+
Maps are unordered, associative collections which map arbitrary keys to values.
224+
Keys must be hashable.
225+
Maps return their count in ``O(1)`` time via :lpy:fn:`count`.
226+
Random access to map values is ``O(log(n))``.
227+
228+
:lpy:fn:`conj` accepts any of the following types, adding new keys or replacing keys as appropriate:
229+
230+
- Another map; values will be merged in from left to right with keys from the rightmost map taking precedence in the instance of a conflict
231+
- A map entry
232+
- 2 element vector; the first element will be treated as the key and the second the value
233+
234+
Calling :lpy:fn:`seq` on a map yields successive map entries, which are roughly equivalent to 2 element vectors.
235+
236+
Maps be called like a function similar to :lpy:fn:`get` with a key and an optional default value, returning the value at the specified key if found.
237+
Returns the default value or ``nil`` (if no default value is specified) otherwise.
238+
239+
.. code-block::
240+
241+
(def m {:a 0 :b 1})
242+
(m :a) ;; => 0
243+
(m :g) ;; => nil
244+
(m :g 5) ;; => 5
245+
246+
.. seealso::
247+
248+
:lpy:fn:`hash-map`, :lpy:fn:`assoc`, :lpy:fn:`assoc-in`, :lpy:fn:`get`, :lpy:fn:`get-in`, :lpy:fn:`find`, :lpy:fn:`update`, :lpy:fn:`update-in`, :lpy:fn:`dissoc`, :lpy:fn:`merge`, :lpy:fn:`merge-with`, :lpy:fn:`map-entry`, :lpy:fn:`key`, :lpy:fn:`val`, :lpy:fn:`keys`, :lpy:fn:`vals`, :lpy:fn:`select-keys`, :lpy:fn:`update-keys`, :lpy:fn:`update-vals`, :lpy:fn:`map?`
249+
250+
.. _sets:
251+
252+
Sets
253+
####
254+
255+
Sets are unordered groups of unique values.
256+
Values must be hashable.
257+
Sets return their count in ``O(1)`` time via :lpy:fn:`count.`
258+
259+
Sets be called like a function similar to :lpy:fn:`get` with a key and an optional default value, returning the value if it exists in the set.
260+
Returns the default value or ``nil`` (if no default value is specified) otherwise.
261+
262+
.. code-block::
263+
264+
(def s #{:a :b :c})
265+
(s :a) ;; => :a
266+
(s :g) ;; => nil
267+
(s :g :g) ;; => :g
268+
269+
.. seealso::
270+
271+
:lpy:fn:`hash-set`, :lpy:fn:`set`, :lpy:fn:`disj`, :lpy:fn:`contains?`, :lpy:fn:`set?`
14272

15273
.. _seqs:
16274

17275
Seqs
18276
----
19277

20-
TBD
278+
Seqs are an interface for sequential types that generalizes iteration to that of a singly-linked list.
279+
However, because the functionality is defined in terms of an interface, many other data types can also be manipulated as Seqs.
280+
The :lpy:fn:`seq` function creates an optimal Seq for the specific input type -- all built-in collection types are "Seqable".
281+
282+
Most of Basilisp's Seq functions operate on Seqs lazily, rather than eagerly.
283+
This is frequently a desired behavior, but can be confusing when debugging or exploring data at the REPL.
284+
You can force a Seq to be fully realized by collecting it into a concrete :ref:`collection type <collection_types>` or by using :lpy:fn:`doall` (among other options).
285+
286+
Seqs bear more than a passing resemblance to a stateful iterator type, but have some distinct advantages.
287+
In particular, Seqs are immutable once realized and thread-safe, meaning Seqs can be be easily passed around with abandon.
288+
289+
Lazy seqs can be created using using the :lpy:fn:`lazy-seq` macro.
290+
291+
.. warning::
292+
293+
There are several possible gotchas when using Seqs over mutable Python :py:class:`collections.abc.Iterable` types.
294+
Because Seqs are immutable, Seqs created from mutable collections can diverge from their source collection if that collection is modified after realizing the Seq.
295+
Also, because Seqs are realized lazily, it is possible that a Seq created from a mutable collection will capture changes to that collection after the initial Seq is created.
21296

22297
.. seealso::
23298

24-
:lpy:fn:`lazy-seq`, :lpy:fn:`seq`, :lpy:fn:`first`, :lpy:fn:`rest`, :lpy:fn:`next`, :lpy:fn:`second`, :lpy:fn:`seq?`, :lpy:fn:`nfirst`, :lpy:fn:`fnext`, :lpy:fn:`nnext`, :lpy:fn:`empty?`, :lpy:fn:`seq?`, :py:class:`basilisp.lang.interfaces.ISeq`
299+
:lpy:fn:`lazy-seq`, :lpy:fn:`seq`, :lpy:fn:`first`, :lpy:fn:`rest`, :lpy:fn:`cons`, :lpy:fn:`next`, :lpy:fn:`second`, :lpy:fn:`seq?`, :lpy:fn:`nfirst`, :lpy:fn:`fnext`, :lpy:fn:`nnext`, :lpy:fn:`empty?`, :lpy:fn:`seq?`, :py:class:`basilisp.lang.interfaces.ISeq`, :py:class:`basilisp.lang.interfaces.ISeqable`
25300

26301
.. _destructuring:
27302

docs/differencesfromclojure.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ In Clojure, this optimistic equality comparison is performed by the ``==`` funct
4343

4444
.. note::
4545

46-
Basilisp's ``=`` will perform as expected when using Python :external:py:class:`decimal.Decimal` typed :ref:`floating_point_numbers`.
46+
Basilisp's ``=`` will perform as expected when using Python :external:py:class:`decimal.Decimal` typed :ref:`floating-point numbers <numbers>`.
4747

4848
.. seealso::
4949

@@ -75,7 +75,7 @@ That said, there are some fundamental differences and omissions in Basilisp that
7575
Reader
7676
------
7777

78-
* :ref:`Numbers <numeric_literals>`
78+
* :ref:`Numbers <reader_numeric_literals>`
7979

8080
* Python integers natively support unlimited precision, so there is no difference between regular integers and those suffixed with ``N`` (which are read as ``BigInt``\s in Clojure).
8181
* Floating point numbers are read as Python ``float``\s by default and subject to the limitations of that type on the current Python VM.
@@ -84,7 +84,7 @@ Reader
8484
* Python natively supports Complex numbers.
8585
The reader will return a complex number for any integer or floating point literal suffixed with ``J``.
8686

87-
* :ref:`Characters <character_literals>`
87+
* :ref:`Characters <reader_character_literals>`
8888

8989
* Python does not support character types, so characters are returned as single-character strings.
9090

docs/pyinterop.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,15 @@ Type hints may be applied to :lpy:form:`def` names, function arguments and retur
243243
.. warning::
244244

245245
Due to the complexity of supporting multi-arity functions in Python, only return annotations are preserved on the arity dispatch function.
246-
Return annotations are combined as by ``typing.Union``, so ``typing.Union[str, str] == str``.
246+
Return annotations are combined as by :external:py:obj:`typing.Union`, so ``typing.Union[str, str] == str``.
247247
The annotations for individual arity arguments are preserved in their compiled form, but they are challenging to access programmatically.
248248

249-
.. _arithmeticdivision:
249+
.. _arithmetic_division:
250250

251-
Arithmetic division
251+
Arithmetic Division
252252
-------------------
253253

254-
:lpy:fn:`basilisp.core/quot`, :lpy:fn:`basilisp.core/rem` and :lpy:fn:`basilisp.core/mod` functions.
254+
.. lpy:currentns:: basilisp.core
255255
256256
The Python native quotient ``//`` and modulo ``%`` operators may yield different results compared to their Java counterpart's long division and modulo operators. The discrepancy arises from Python's choice of floored division (`src <http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html>`_, `archived <https://web.archive.org/web/20100827160949/http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html>`_) while Java employs truncated division for its calculations (refer to the to the `Wikipedia Modulo page <https://en.wikipedia.org/wiki/Modulo>`_ for a a comprehensive list of available division formulae).
257257

@@ -260,3 +260,7 @@ In Clojure, the ``clojure.core/quot`` function utilizes Java's long division ope
260260
Basilisp has chosen to adopt the same mathematical formulae as Clojure for these three functions, rather than using the Python's built in operators under all cases. This approach offers the advantage of enhanced cross-platform compatibility without requiring modification, and ensures compatibility with examples in `ClojureDocs <https://clojuredocs.org/>`_.
261261

262262
Users still have the option to use the native :external:py:func:`operator.floordiv`, i.e. Python's ``//`` operator, if they prefer so.
263+
264+
.. seealso::
265+
266+
:lpy:fn:`quot`, :lpy:fn:`rem`, :lpy:fn:`mod`

0 commit comments

Comments
 (0)