2016-06-15 13:37:23 +00:00
<!-- markdown - toc start - Don't edit this section. Run M - x markdown - toc - generate - toc again -->
**Table of Contents**
2016-06-15 13:39:45 +00:00
- [Core Macros ](#core-macros )
2016-06-16 21:05:38 +00:00
- [collected? ](#collected )
2016-07-30 14:47:31 +00:00
- [multi-transform ](#multi-transform )
2016-06-15 13:39:45 +00:00
- [replace-in ](#replace-in )
- [select ](#select )
2016-06-16 21:05:38 +00:00
- [select-any ](#select-any )
- [selected-any? ](#selected-any )
2016-06-15 13:39:45 +00:00
- [select-first ](#select-first )
- [select-one ](#select-one )
2018-02-12 11:42:51 +00:00
- [select-one! ](#select-one-1 )
2016-06-15 13:39:45 +00:00
- [setval ](#setval )
- [transform ](#transform )
2016-06-16 21:05:38 +00:00
- [traverse ](#traverse )
2017-09-21 16:19:07 +00:00
- [traverse-all ](#traverse-all )
2016-06-15 13:39:45 +00:00
- [Path Macros ](#path-macros )
- [declarepath ](#declarepath )
- [defprotocolpath ](#defprotocolpath )
- [extend-protocolpath ](#extend-protocolpath )
- [path ](#path )
- [providepath ](#providepath )
2017-08-21 12:45:57 +00:00
- [recursive-path ](#recursive-path )
2016-06-15 13:39:45 +00:00
- [Collector Macros ](#collector-macros )
- [defcollector ](#defcollector )
- [Navigator Macros ](#navigator-macros )
2017-10-01 17:47:23 +00:00
- [defdynamicnav ](#defdynamicnav )
2016-06-15 13:39:45 +00:00
- [defnav ](#defnav )
- [nav ](#nav )
2016-06-15 13:37:23 +00:00
<!-- markdown - toc end -->
2016-06-15 13:38:38 +00:00
# Core Macros
2016-06-15 11:15:32 +00:00
2016-06-16 21:05:38 +00:00
## collected?
`(collected? params & body)`
_Added in 0.12.0_
Creates a filter function navigator that takes in all the collected values
as input. For arguments, can use `(collected? [a b] ...)` syntax to look
at each collected value as individual arguments, or `(collected? v ...)` syntax
to capture all the collected values as a single vector.
`collected?` operates in the same fashion as [pred ](List-of-Navigators#pred ), but it takes the collected values as its arguments rather than the structure.
```clojure
=> (select [ALL (collect-one FIRST) LAST (collected? [k] (= k :a))] {:a 0 :b 1})
[[:a 0]]
=> (select [ALL (collect-one FIRST) LAST (collected? [k] (< k 2 ) ) ]
(zipmap (range 5) ["a" "b" "c" "d" "e"]))
[[0 "a"] [1 "b"]]
=> (transform [ALL (collect-one FIRST) LAST (collected? [k] (< k 2 ) ) DISPENSE ]
string/upper-case
(zipmap (range 5) ["a" "b" "c" "d" "e"]))
{0 "A", 1 "B", 2 "c", 3 "d", 4 "e"}
```
2016-07-30 14:47:31 +00:00
## multi-transform
`(multi-transform path structure)`
_Added in 0.12.0_
Just like `transform` but expects transform functions to be specified inline in
the path using `terminal` . Error is thrown if navigation finishes at a
non-`terminal` navigator. `terminal-val` is a wrapper around `terminal` and is
the `multi-transform` equivalent of `setval` . Much more efficient than doing the
transformations with `transform` one after another when the transformations
2017-03-03 20:37:24 +00:00
share a lot of navigation. This macro will do inline compilation and caching of the path.
2016-07-30 14:47:31 +00:00
```clojure
(multi-transform [:a :b (multi-path [:c (terminal-val :done)]
[:d (terminal inc)]
[:e (putval 3) (terminal +)])]
{:a {:b {:c :working :d 0 :e 1.5}}})
{:a {:b {:c :done, :d 1, :e 4.5}}}
```
2016-07-30 14:50:10 +00:00
See also [terminal ](List-of-Navigators#terminal ) and [terminal-val ](List-of-Navigators#terminal-val ).
2016-07-30 14:47:31 +00:00
2016-06-15 13:38:38 +00:00
## replace-in
2016-06-15 12:07:38 +00:00
`(replace-in apath transform-fn structure & args)`
2016-06-15 18:43:29 +00:00
Similar to `transform` , except returns a pair of `[transformed-structure sequence-of-user-ret]` .
The transform-fn in this case is expected to return `[ret user-ret]` . ret is
what's used to transform the data structure, while `user-ret` will be added to the `user-ret` sequence
2016-06-15 12:07:38 +00:00
in the final return. replace-in is useful for situations where you need to know the specific values
of what was transformed in the data structure.
2017-03-03 04:21:26 +00:00
This macro will do inline factoring and caching of the path.
2016-06-15 12:07:38 +00:00
Note that the `user-ret` portion of the return value of `transform-fn` must be a sequence in order to be joined onto all previous user-return values.
`replace-in` takes an optional argument `:merge-fn` . `merge-fn` takes two arguments `[curr-user-ret new-user-ret]` and should return a new user-return value. If no user-return values have been added, `user-ret` will be `nil` .
```clojure
;; double and save evens
=> (replace-in [ALL even?] (fn [x] [(* 2 x) [x]]) (range 10))
[(0 1 4 3 8 5 12 7 16 9) (0 2 4 6 8)]
;; double evens and save largest even
=> (replace-in [ALL even?] (fn [x] [(* 2 x) x]) [3 2 8 5 6]
:merge-fn (fn [curr new] (if (nil? curr) new (max curr new))))
[[3 4 16 5 12] 8]
```
2016-06-15 13:38:38 +00:00
## select
2016-06-15 11:15:32 +00:00
`(select apath structure)`
Navigates to and returns a sequence of all the elements specified by the path.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-15 11:15:32 +00:00
```clojure
=> (select [ALL even?] (range 10))
[0 2 4 6 8]
=> (select :a {:a 0 :b 1})
[0]
=> (select ALL {:a 0 :b 1})
[[:a 0] [:b 1]]
```
2016-06-16 21:05:38 +00:00
## select-any
`(select-any apath structure)`
_Added in 0.12.0_
Returns any element found or `com.rpl.specter/NONE` if nothing selected. This is the most
efficient of the various selection operations.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-16 21:05:38 +00:00
```clojure
=> (select-any STAY :a)
:a
=> (select-any even? 3)
:com.rpl.specter.impl/NONE ; Implementation detail
=> (= com.rpl.specter/NONE :com.rpl.specter.impl/NONE)
true
```
## selected-any?
`(selected-any? apath structure)`
_Added in 0.12.0_
Returns true if any element was selected, false otherwise.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-16 21:05:38 +00:00
```clojure
=> (selected-any? STAY :a)
true
=> (selected-any? even? 3)
false
=> (selected-any? ALL (range 10))
true
=> (selected-any? ALL [])
false
```
2016-06-15 13:38:38 +00:00
## select-first
2016-06-15 12:07:38 +00:00
`(select-first apath structure)`
2016-06-15 18:43:29 +00:00
Returns first element found. Not any more efficient than `select` , just a convenience.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-15 12:07:38 +00:00
```clojure
=> (select-first ALL (range 10))
0
;; Returns the result itself if the result is not a sequence
=> (select-first FIRST (range 10))
0
```
2016-06-15 13:38:38 +00:00
## select-one
2016-06-15 12:07:38 +00:00
`(select-one apath structure)`
2016-06-15 18:43:29 +00:00
Like `select` , but returns either one element or nil. Throws exception if multiple elements found.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-15 12:07:38 +00:00
```clojure
;; srange returns one collection
=> (select (srange 2 7) (range 10))
[[2 3 4 5 6]]
=> (select-one (srange 2 7) (range 10))
[2 3 4 5 6]
=> (select-one ALL (range 10))
IllegalArgumentException More than one element found for params
```
2016-06-15 13:38:38 +00:00
## select-one!
2016-06-15 12:07:38 +00:00
`(select-one! apath structure)`
Returns exactly one element, throws exception if zero or multiple elements found.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-15 12:07:38 +00:00
```clojure
=> (select-one! FIRST (range 5))
0
;; zero results, throws exception
=> (select-one! [ALL even? odd?] (range 10))
IllegalArgumentException Expected exactly one element for params
;; multiple results, throws exception
=> (select-one! [ALL even?] (range 10))
IllegalArgumentException Expected exactly one element for params
```
2016-06-15 13:38:38 +00:00
## setval
2016-06-15 12:07:38 +00:00
`(setval apath aval structure)`
Navigates to each value specified by the path and replaces it by `aval` .
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-15 12:07:38 +00:00
```clojure
=> (setval [ALL even?] :even (range 10))
(:even 1 :even 3 :even 5 :even 7 :even 9)
```
2016-06-15 13:38:38 +00:00
## transform
2016-06-15 12:07:38 +00:00
`(transform apath transform-fn structure)`
Navigates to each value specified by the path and replaces it by the result of running
the transform-fn on it.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-15 12:07:38 +00:00
```clojure
=> (transform [ALL] #(* % 2) (range 10))
(0 2 4 6 8 10 12 14 16 18)
;; putval collects its argument
=> (transform [(putval 2) ALL] * (range 10))
(0 2 4 6 8 10 12 14 16 18)
=> (transform [(putval 2) (walker #(and (integer? %) (even? %)))] * [[[[1] 2]] 3 4 [5 6] [7 [[8]]]])
[[[[1] 4]] 3 8 [5 12] [7 [[16]]]]
=> (transform [ALL] (fn [[k v]] [k {:key k :val v}]) {:a 0 :b 1})
{:a {:key :a, :val 0}, :b {:key :b, :val 1}}
```
2016-06-16 21:05:38 +00:00
## traverse
`(traverse apath structure)`
_Added in 0.12.0_
Return a reducible object that traverses over `structure` to every element
specified by the path.
2017-03-03 20:37:24 +00:00
This macro will do inline compilation and caching of the path.
2016-06-16 21:05:38 +00:00
`(reduce afn init (traverse apath structure))` will always return the same thing as `(reduce afn init (select apath structure))` , but more efficiently. The return value of `traverse` is only useful as an argument to `reduce` ; for all other uses, prefer [select ](#select ).
```clojure
=> (reduce + 0 (traverse ALL (range 10)))
45
=> (reduce + 0 (traverse (walker integer?) [[[1 2]] 3 [4 [[5 6 7]] 8] 9]))
45
2017-05-01 18:45:55 +00:00
=> (into #{} (traverse (walker integer?) [[1 2] 1 [[3 [4 4 [2]]]]]))
2016-06-16 21:45:09 +00:00
#{1 4 3 2}
2016-06-16 21:05:38 +00:00
=> (traverse (walker integer?) [[[1 2]] 3 [4 [[5 6 7]] 8] 9])
;; returns object implementing clojure.lang.IReduce
```
2017-09-21 12:45:33 +00:00
## traverse-all
_Added in 1.0.0_
`(traverse-all apath)`
Returns a transducer that traverses over each element with the given path.
2017-09-21 12:47:20 +00:00
Many common transducer use cases can be expressed more elegantly with traverse-all:
```clojure
;; Using Vanilla Clojure
(transduce
(comp (map :a) (mapcat identity) (filter odd?))
+
[{:a [1 2]} {:a [3]} {:a [4 5]}])
;; => 9
;; The same logic expressed with Specter
(transduce
(traverse-all [:a ALL odd?])
+
[{:a [1 2]} {:a [3]} {:a [4 5]}])
2017-09-21 12:50:16 +00:00
;; => 9
2017-09-21 12:47:20 +00:00
```
2017-09-21 12:52:18 +00:00
Here are some more examples of using traverse-all:
```clojure
=> (into [] (traverse-all :a) [{:a 1} {:a 2}])
[1 2]
=> (transduce (traverse-all [ALL :a])
+
0
[[{:a 1} {:a 2}] [{:a 3}]])
6
=> (transduce (comp (mapcat identity)
(traverse-all :a))
(completing (fn [r i] (if (= i 4) (reduced r) (+ r i))))
0
[[{:a 1}] [{:a 2}] [{:a 4}] [{:a 5}]])
3
```
2016-06-15 13:38:38 +00:00
# Path Macros
2016-06-12 20:07:45 +00:00
2016-06-15 13:38:38 +00:00
## declarepath
2016-06-12 20:07:45 +00:00
`(declarepath name)`
2016-06-12 20:11:22 +00:00
2016-06-12 20:07:45 +00:00
`(declarepath name params)`
2016-07-28 14:10:12 +00:00
Declares a new symbol available to be defined as a path. If the path will require parameters, these must be specified here. The path itself must be defined using [providepath ](#providepath ). `declarepath` and `providepath` are great for defining recursive navigators, as seen in the second example below.
2016-06-12 20:07:45 +00:00
```clojure
=> (declarepath SECOND)
=> (providepath SECOND [(srange 1 2) FIRST])
=> (select-one SECOND (range 5))
1
=> (transform SECOND dec (range 5))
(0 0 2 3 4)
2016-07-28 14:10:12 +00:00
=> (declarepath DEEP-MAP-VALS)
=> (providepath DEEP-MAP-VALS (if-path map? [MAP-VALS DEEP-MAP-VALS] STAY))
=> (select DEEP-MAP-VALS {:a {:b 2} :c {:d 3 :e {:f 4}} :g 5})
[2 3 4 5]
=> (transform DEEP-MAP-VALS inc {:a {:b 2} :c {:d 3 :e {:f 4}} :g 5})
{:a {:b 3}, :c {:d 4, :e {:f 5}}, :g 6}
2016-06-12 20:07:45 +00:00
```
2016-06-15 13:38:38 +00:00
## defprotocolpath
2016-06-13 02:49:19 +00:00
`(defprotocolpath name)`
`(defprotocolpath name params)`
Defines a navigator that chooses the path to take based on the type
of the value at the current point. May be specified with parameters to
specify that all extensions must require that number of parameters.
Currently not available for ClojureScript.
```clojure
=> (defrecord SingleAccount [funds])
=> (defrecord FamilyAccount [single-accounts])
=> (defprotocolpath FundsPath)
=> (extend-protocolpath FundsPath
SingleAccount :funds
FamilyAccount [:single-accounts ALL FundsPath])
=> (select [ALL FundsPath]
[(->SingleAccount 100) (->SingleAccount 3)
(->FamilyAccount [(->SingleAccount 15) (->SingleAccount 12)])])
[100 3 15 12]
2016-06-13 03:06:16 +00:00
=> (defprotocolpath AfterFeePath [fee-fn])
=> (extend-protocolpath AfterFeePath
SingleAccount [:funds view]
FamilyAccount [:single-accounts ALL AfterFeePath])
=> (select [ALL (AfterFeePath dec)]
[(->SingleAccount 100) (->SingleAccount 3)
(->FamilyAccount [(->SingleAccount 15) (->SingleAccount 12)])])
[99 2 14 11]
2016-06-13 02:49:19 +00:00
```
2016-06-15 13:38:38 +00:00
## extend-protocolpath
2016-06-13 02:49:19 +00:00
`(extend-protocolpath protpath & extensions)`
Extends a protocol path `protpath` to a list of types. The `extensions` argument has the form `type1 path1 type2 path2...` .
See [defprotocolpath ](#defprotocolpath ) for an example.
2016-06-15 13:38:38 +00:00
## path
2016-06-15 13:37:23 +00:00
`(path & path)`
2016-06-15 18:43:29 +00:00
Same as calling `comp-paths` , except it caches the composition of the static part
2016-06-15 13:37:23 +00:00
of the path for later re-use (when possible). For almost all idiomatic uses
of Specter provides huge speedup. This macro is automatically used by the
select/transform/setval/replace-in/etc. macros.
2017-01-24 13:54:33 +00:00
Any higher order navigators passed to `path` must include their arguments, even if their arguments will be evaluated at runtime.
2016-06-15 18:23:15 +00:00
2016-06-15 13:37:23 +00:00
```clojure
2017-10-07 00:17:20 +00:00
=> (def MY-PATH (path even?))
=> (select [ALL MY-PATH] (range 10))
2016-06-15 18:23:15 +00:00
[0 2 4 6 8]
2016-06-15 13:37:23 +00:00
```
2016-06-15 13:38:38 +00:00
## providepath
2016-06-12 23:29:07 +00:00
`(providepath name apath)`
Defines the path that will be associated to the provided name. The name must have been previously declared using [declarepath ](#declarepath ).
2017-08-21 12:45:57 +00:00
## recursive-path
`(recursive-path params self-sym path)`
2017-09-18 13:12:11 +00:00
Assists in making recursive paths, both parameterized and unparameterized.
Here is an example of using `recursive-path` without parameters to select and transform:
2017-08-21 12:45:57 +00:00
```clojure
2017-09-18 13:12:11 +00:00
=> (def tree-walker (recursive-path [] p (if-path vector? [ALL p] STAY)))
#'playground.specter/tree-walker
;; Get all of the values nested within vectors
=> (select tree-walker [1 [2 [3 4] 5] [[6]]])
2017-08-21 12:45:57 +00:00
[1 2 3 4 5 6]
2017-09-18 13:12:11 +00:00
;; Transform all of the values within vectors
=> (transform tree-walker inc [1 [2 [3 4] 5] [[6]]])
[2 [3 [4 5] 6] [[7]]]
```
And here is an example of using `recursive-path` with parameters to select and transform:
```clojure
=> (def map-key-walker (recursive-path [akey] p [ALL (if-path [FIRST #(= % akey)] LAST [LAST p])]))
#'playground.specter/map-key-walker
;; Get all the vals for key :aaa, regardless of where they are in the structure
=> (select (map-key-walker :aaa) {:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}})
[3 2 1]
;; Transform all the vals for key :aaa, regardless of where they are in the structure
=> (transform (map-key-walker :aaa) inc {:a {:aaa 3 :b {:c {:aaa 2} :aaa 1}}})
{:a {:aaa 4, :b {:c {:aaa 3}, :aaa 2}}}
2017-08-21 12:45:57 +00:00
```
2016-06-15 13:38:38 +00:00
# Collector Macros
2016-06-12 20:11:22 +00:00
2016-06-15 13:38:38 +00:00
## defcollector
2016-06-12 20:07:45 +00:00
`(defcollector name params collect-val-impl)`
Defines a collector with the given name and parameters. Collectors are navigators which add a value to the list of collected values and do not change the current structure.
2016-06-12 23:29:07 +00:00
Note that `params` should be a vector, as would follow `fn` .
`collect-val-impl` must be of the form `(collect-val [this structure] body)` . It should return the value to be collected.
2016-06-12 20:07:45 +00:00
An informative example is the actual implementation of `putval` , which follows.
```clojure
=> (defcollector putval [val]
(collect-val [this structure]
val))
=> (transform [ALL (putval 3)] + (range 5))
(3 4 5 6 7)
```
2016-06-15 13:38:38 +00:00
# Navigator Macros
2016-06-12 20:11:22 +00:00
2017-10-01 17:47:23 +00:00
## defdynamicnav
`(defdynamicnav name & args)`
Defines a function that can choose what navigator to use at runtime based on the dynamic context. The arguments will either be static values or objects satisfying `dynamic-param?` . Use `late-bound-nav` to produce a runtime navigator that uses the values of the dynamic params. See `selected?` for an illustrative example of dynamic navs. Also see [Specter's inline caching implementation ](https://github.com/nathanmarz/specter/wiki/Specter%27s-inline-caching-implementation ).
2016-06-15 13:38:38 +00:00
## defnav
2016-06-12 20:07:45 +00:00
`(defnav name params select-impl transform-impl)`
2016-06-12 20:11:22 +00:00
2016-06-12 20:07:45 +00:00
`(defnav name params transform-impl select-impl)`
Canonically the first is used.
Defines a navigator with given name and parameters. Note that `params` should be a vector,
as would follow `fn` .
2016-06-14 21:03:28 +00:00
`select-impl` must be of the form `(select* [this structure next-fn] body)` . It should return the result of calling `next-fn` on whatever subcollection of `structure` this navigator selects.
2016-06-12 20:07:45 +00:00
2016-06-14 21:03:28 +00:00
`transform-impl` must be of the form `(transform* [this structure next-fn] body)` . It should find the result of calling `nextfn` on whatever subcollection of `structure` this navigator selects. Then it should return the result of reconstructing the original structure using the results of the `nextfn` call.
2016-06-12 20:07:45 +00:00
See also [nav ](#nav )
```clojure
=> (defnav nth-elt [n]
(select* [this structure next-fn] (next-fn (nth structure n)))
(transform* [this structure next-fn]
(let [structurev (vec structure)
ret (next-fn (nth structure n))]
(if (vector? structure)
(assoc structurev n ret)
(concat (take n structure) (list ret) (drop (inc n) structure))))))
=> (select-one (nth-elt 0) (range 5))
0
=> (select-one (nth-elt 3) (range 5))
3
=> (select-one (nth-elt 3) (range 0 10 2))
6
=> (transform (nth-elt 1) inc (range 5))
(0 2 2 3 4)
=> (transform (nth-elt 1) inc (vec (range 5)))
[0 2 2 3 4]
```
2016-06-15 13:38:38 +00:00
## nav
2016-06-12 20:07:45 +00:00
`(nav params select-impl transform-impl)`
2016-06-12 20:11:22 +00:00
2016-06-12 20:07:45 +00:00
`(nav params transform-impl select-impl)`
2017-08-21 12:45:57 +00:00
Returns an "anonymous navigator." See [defnav ](#defnav ).