io.github.frenchy64.fully-satisfies.safer

Variants of clojure.core functions that improve thread-safety and general robustness
when passed mutating collections.

We agree that 'Robust programs should not mutate arrays or Iterables that have seqs on them.'
https://clojure.org/reference/sequences
Eductions inhabit a middle ground and might be the most practical application of this namespace.
They are designed to be walked from first to last like seqs, but each element is recomputed
instead of being cached like persistent seqs. This becomes problematic if a sequence function
walks its argument multiple times without first binding a seq.

For example, clojure.core/split-at could disagree on the take/drop parts of
the collection if the coll is mutated between realizing the splits.
Here, any one call to the eduction alternates between [0 1 2 3 4 5 6 7 8 9] and
[9 8 7 6 5 4 3 2 1 0]. However, split-at incorrectly splits the eduction as
[[0 1 2 3 4] [4 3 2 1 0]], and safer/split-at returns one of the two correct splits:
[[0 1 2 3 4] [5 6 7 8 9]].

(deftest split-at-mutation-test
  (let [up-down (atom true)
        ed (eduction (map (fn [i]
                            (when (zero? i)
                              (swap! up-down not))
                            (if @up-down
                              (- 9 i)
                              i)))
                     (range 10))]
    (is (= [[0 1 2 3 4] [4 3 2 1 0]] (split-at 5 ed)))
    (is (= [[0 1 2 3 4] [5 6 7 8 9]] (safer/split-at 5 ed)))))

See io.github.frenchy64.fully-satisfies.safer-test for more details.

The basic trick here is strategically calling seq earlier on the collection argument.

butlast

added in 1.0

(butlast coll)
Return a seq of all but the last item in coll, in linear time

safer/butlast additionally:
- is threadsafe for mutable collections, at the cost of an additional
  call to seq.
- calls next once per element instead of twice.

drop-last

added in 1.0

(drop-last coll)(drop-last n coll)
Return a lazy sequence of all but the last n (default 1) items in coll

safer/drop-last additionally is thread-safe for mutable collections,
at the cost of a call to seq.

every?

added in 1.0

(every? pred coll)
Returns true if (pred x) is logical true for every x in coll, else
false.

safer/every? additionally is thread-safe for mutable collections.

last

added in 1.0

(last coll)
Return the last item in coll, in linear time

safer/last additionally:
- is thread-safe for mutable collections, at the cost of a call to seq.
- calls next once every step instead of twice.

not-every?

added in 1.0

(not-every? pred coll)
Returns false if (pred x) is logical true for every x in
coll, else true.
      
safer/not-every? additionally is thread-safe for mutable collections.

partitionv-all

added in 1.12

(partitionv-all n)(partitionv-all n coll)(partitionv-all n step coll)
Returns a lazy sequence of vector partitions, but may include
partitions with fewer than n items at the end.
Returns a stateful transducer when no collection is provided.

safer/partitionv-all additionally is thread-safe for mutable collections
and generally robust against a mutating coll.

sort

added in 1.0

(sort coll)(sort comp coll)
Returns a sorted sequence of the items in coll. If no comparator is
supplied, uses compare.  comparator must implement
java.util.Comparator.  Guaranteed to be stable: equal elements will
not be reordered.  If coll is a Java array, it will be modified.  To
avoid this, sort a copy of the array.

safer/sort additionally is thread-safe for mutable collections (avoids NPE).

sort-by

added in 1.0

(sort-by keyfn coll)(sort-by keyfn comp coll)
Returns a sorted sequence of the items in coll, where the sort
order is determined by comparing (keyfn item).  If no comparator is
supplied, uses compare.  comparator must implement
java.util.Comparator.  Guaranteed to be stable: equal elements will
not be reordered.  If coll is a Java array, it will be modified.  To
avoid this, sort a copy of the array.

safer/sort-by additionally is thread-safe for mutable collections (avoids NPE).

split-at

added in 1.0

(split-at n coll)
Returns a vector of [(take n coll) (drop n coll)]

safer/split-at additionally is thread-safe for mutable collections
and generally robust against a mutating coll at the cost of a call to seq.

split-with

added in 1.0

(split-with pred coll)
Returns a vector of [(take-while pred coll) (drop-while pred coll)]

safer/split-with additionally is thread-safe for mutable collections,
and generally robust against a mutating coll at the cost of a call to seq.

splitv-at

added in 1.12

(splitv-at n coll)
Returns a vector of [(into [] (take n) coll) (drop n coll)]

safer/splitv-at additionally is thread-safe for mutable collections
and generally robust against a mutating coll.

take-last

added in 1.1

(take-last n coll)
Returns a seq of the last n items in coll.  Depending on the type
of coll may be no better than linear time.  For vectors, see also subvec.

safer/drop-last additionally is thread-safe for mutable collections.