Compare commits
115 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a615ff22c | ||
|
|
67e8680602 | ||
|
|
05ae730896 | ||
|
|
a91aaaa34a | ||
|
|
efeefdfacd | ||
|
|
a64209d582 | ||
|
|
d0d6fdf581 | ||
|
|
e8225f0e58 | ||
|
|
a379893598 | ||
|
|
efaf35558a | ||
|
|
083376f904 | ||
|
|
de48f24d70 | ||
|
|
c54a46c686 | ||
|
|
2e002c1270 | ||
|
|
789881b3ad | ||
|
|
edd7429281 | ||
|
|
e222ba2a6c | ||
|
|
40add561b6 | ||
|
|
123af0937e | ||
|
|
6e7d772755 | ||
|
|
134beac2a7 | ||
|
|
86f8412ee4 | ||
|
|
28ef42152a | ||
|
|
ffe8130bf0 | ||
|
|
9c7f6fb65e | ||
|
|
029a33427f | ||
|
|
925e2e91d6 | ||
|
|
29ee91d01e | ||
|
|
b7ff05bf54 | ||
|
|
ae24856ec3 | ||
|
|
23f4e1a370 | ||
|
|
e38a2561d8 | ||
|
|
fe1deb5195 | ||
|
|
7790213b16 | ||
|
|
8764a4b2b6 | ||
|
|
c65b31181a | ||
|
|
f13b5db08a | ||
|
|
6c3253f16a | ||
|
|
e7abb2b538 | ||
|
|
3536e3c461 | ||
|
|
350c8b857f | ||
|
|
798cda211f | ||
|
|
4580de8cc6 | ||
|
|
79a3610a64 | ||
|
|
1590b2c2d4 | ||
|
|
efe94b6539 | ||
|
|
eea5fcb48b | ||
|
|
ac48127871 | ||
|
|
01617b6264 | ||
|
|
38a27ecc43 | ||
|
|
d4887c2090 | ||
|
|
4a589c3074 | ||
|
|
1a05546f27 | ||
|
|
d5143f136c | ||
|
|
f7b7b0cba2 | ||
|
|
05e354a9a2 | ||
|
|
b4a3c4f601 | ||
|
|
c81a4b1a7b | ||
|
|
e92fd674b1 | ||
|
|
c2f669db71 | ||
|
|
09d0d071ef | ||
|
|
70d9fef5cc | ||
|
|
b1050b910c | ||
|
|
7b646ca566 | ||
|
|
6a5054feea | ||
|
|
632e710b07 | ||
|
|
7bd119aa52 | ||
|
|
0ceda21151 | ||
|
|
9515582a19 | ||
|
|
680c36ae5b | ||
|
|
d54aa28d49 | ||
|
|
3478e5b6d7 | ||
|
|
5b60eb17e3 | ||
|
|
349e03342f | ||
|
|
844050545a | ||
|
|
5efafd2d9b | ||
|
|
98c7510d1c | ||
|
|
efaeff4fc5 | ||
|
|
9a8f79774c | ||
|
|
fdfaecd0d0 | ||
|
|
4cf7ee965f | ||
|
|
f593753a68 | ||
|
|
0e88c57a87 | ||
|
|
14dad51fd4 | ||
|
|
5aed3b254e | ||
|
|
d7ee2f7c6a | ||
|
|
7dbe4dc524 | ||
|
|
dc9e2205c3 | ||
|
|
6e90ceadea | ||
|
|
b5f840db22 | ||
|
|
796ad700f7 | ||
|
|
3a6d8620d7 | ||
|
|
92caabbb32 | ||
|
|
49eed0079c | ||
|
|
915c0a9471 | ||
|
|
86033f3df1 | ||
|
|
ac3fe713ac | ||
|
|
efd123d17e | ||
|
|
c233fb7e9d | ||
|
|
8d5f39a861 | ||
|
|
09d00ac7e7 | ||
|
|
e2308e0cda | ||
|
|
31935fca2e | ||
|
|
85d3f14de9 | ||
|
|
43fd7ab2e2 | ||
|
|
1805094df8 | ||
|
|
1b4a7d3d59 | ||
|
|
1b7f987eaa | ||
|
|
03e686cd9c | ||
|
|
dbae629472 | ||
|
|
40c883c1f3 | ||
|
|
b7c62e444c | ||
|
|
179b705211 | ||
|
|
b18733b14e | ||
|
|
179d818503 |
18 changed files with 936 additions and 291 deletions
37
.github/workflows/ci.yml
vendored
Normal file
37
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch_depth: 0
|
||||
|
||||
- name: Setup Babashka
|
||||
uses: turtlequeue/setup-babashka@v1.3.0
|
||||
with:
|
||||
babashka-version: 0.7.8
|
||||
|
||||
- name: Prepare java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '8'
|
||||
|
||||
- name: Install clojure tools
|
||||
uses: DeLaGuardo/setup-clojure@4.0
|
||||
with:
|
||||
lein: 2.9.1
|
||||
|
||||
- name: Run clj tests
|
||||
run: bb test:clj
|
||||
|
||||
- name: Run cljs tests
|
||||
run: bb test:cljs
|
||||
|
||||
- name: Run babashka tests
|
||||
run: bb test:bb
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -11,3 +11,5 @@ pom.xml.asc
|
|||
.lein-failures
|
||||
.cljs_node_repl
|
||||
out/
|
||||
.cpcache
|
||||
.cache
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
language: clojure
|
||||
lein: 2.7.1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
script:
|
||||
- lein test
|
||||
- lein doo node test-build once
|
||||
127
CHANGES.md
127
CHANGES.md
|
|
@ -1,6 +1,54 @@
|
|||
## 1.0.3-SNAPSHOT
|
||||
## 1.1.4
|
||||
|
||||
## 1.0.2
|
||||
* Add arglist metadata to navs (thanks @phronmophobic)
|
||||
* Improve before-index performance by 150x on lists and 5x on vectors (thanks @jeff303)
|
||||
* Bug fix: BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs
|
||||
* Add compatibility with [babashka](https://babashka.org/)
|
||||
|
||||
## 1.1.3 - 2019-10-13
|
||||
|
||||
* Better AOT behavior: path functions for inline caching and protpath extensions no longer write eval'd class files. You can force path functions to not override `*compile-files*` by binding `com.rpl.specter.impl/*path-compile-files*` to `true`.
|
||||
* Bug fix: fix throw-illegal in cljs
|
||||
|
||||
## 1.1.2 - 2018-11-01
|
||||
|
||||
* Eliminate reflection warning
|
||||
* Bug fix: Fix inline compiler symbol handling so class references can be used as constants within paths
|
||||
* Bug fix: Fix handling of subvector paths in cljs
|
||||
|
||||
## 1.1.1 - 2018-04-23
|
||||
|
||||
* ClojureScript 1.10 introduced a change causing the `walker` navigator to fail to walk records. `ALL` has been updated to operate over `MapEntry` in ClojureScript, fixing the issue.
|
||||
* Change ns form to comply with cljs.core.specs.alpha (thanks @gnl)
|
||||
* Remove usage of pprint in cljs so advanced optimization doesn't include it
|
||||
|
||||
## 1.1.0 - 2018-01-02
|
||||
|
||||
* Add `vtransform` variant of `transform` that takes in collected values as a vector in the first argument rather than spliced into argument list.
|
||||
* Add `vterminal` that takes in collected vals as vector in first argument rather than spliced into argument list.
|
||||
* Add `compact` navigator. After each step of navigation of its subpath, `compact` removes the collection if it's empty.
|
||||
* Change `terminal` to be a no-op on select codepath
|
||||
* Bug fix: `subselect`/`filterer` removes first matched element instead of setting to nil when transformed to empty sequence
|
||||
|
||||
## 1.0.5 - 2017-11-16
|
||||
|
||||
* Add `regex-nav` navigator for regexes, which navigates to every match in a string and supports replacement with a new substring (thanks @mwfogleman)
|
||||
* Regexes implicitly convert to `regex-nav` in paths
|
||||
* Strings, numbers, booleans, characters, and symbols implicitly convert to `keypath` in paths
|
||||
|
||||
## 1.0.4 - 2017-10-17
|
||||
|
||||
* Add `indexed-vals` navigator, a variant of `INDEXED-VALS` that allows for a customized start index.
|
||||
* Bug fix: Fix `INDEXED-VALS` invalidly overwriting elements in some transforms involving multiple index changes
|
||||
|
||||
## 1.0.3 - 2017-08-14
|
||||
|
||||
* Added `before-index` navigator for inserting a single element into a sequence.
|
||||
* Added `index-nav` navigator for moving an element in a sequence to a new index, shifting other elements in the process.
|
||||
* Added `INDEXED-VALS` navigator for navigating to every element of a sequence as [index elem] pair. Transform on index portion works the same as `index-nav`.
|
||||
* Workaround for ClojureScript regression that causes warnings for record fields named "var" or other reserved names
|
||||
|
||||
## 1.0.2 - 2017-06-12
|
||||
|
||||
* Added `pred=`, `pred<`, `pred>`, `pred<=`, `pred>=` for filtering using common comparisons
|
||||
* Add `map-key` navigator
|
||||
|
|
@ -14,7 +62,7 @@
|
|||
* For ALL transforms on maps, interpret transformed key/value pair of size < 2 as removal
|
||||
* Bug fix: Fix incorrect inline compilation when a dynamic function invocation is nested in a data structure within a parameter to a navigator builder
|
||||
|
||||
## 1.0.1
|
||||
## 1.0.1 - 2017-04-17
|
||||
|
||||
* `subselect`/`filterer` can remove entries in source by transforming to a smaller sequence
|
||||
* Add `satisfies-protpath?`
|
||||
|
|
@ -24,7 +72,7 @@
|
|||
* Bug fix: Dynamic navs in argument position to another nav now properly expanded and compiled
|
||||
* Bug fix: Dynamic parameters nested inside data structures as arguments are now compiled correctly by inline compiler
|
||||
|
||||
## 1.0.0
|
||||
## 1.0.0 - 2017-03-01
|
||||
|
||||
* Transform to `com.rpl.specter/NONE` to remove elements from data structures. Works with `keypath` (for both sequences and maps), `must`, `nthpath`, `ALL`, `MAP-VALS`, `FIRST`, and `LAST`
|
||||
* Add `nthpath` navigator
|
||||
|
|
@ -43,13 +91,13 @@
|
|||
* Bug fix: Collected vals now properly passed to subpaths for `if-path`, `selected?`, and `not-selected?`
|
||||
* Bug fix: `LAST`, `FIRST`, `BEGINNING`, and `END` properly transform subvector types to a vector type
|
||||
|
||||
## 0.13.2
|
||||
## 0.13.2 - 2016-12-21
|
||||
|
||||
* Bug fix: Fix race condition relating to retrieving path from cache and AOT compilation
|
||||
* Bug fix: LAST no longer converts lists to vectors
|
||||
* Bug fix: Workaround issue with aot + uberjar
|
||||
|
||||
## 0.13.1
|
||||
## 0.13.1 - 2016-11-07
|
||||
|
||||
* Remove any? in com.rpl.specter.impl to avoid conflict with Clojure 1.9
|
||||
* Enhanced dynamic navigators to continue expanding if any other dynamic navs are returned
|
||||
|
|
@ -60,7 +108,7 @@
|
|||
* Bug fix: Fix problems with multi-path and if-path in latest versions of ClojureScript
|
||||
* Bug fix: Inline compiler no longer flattens and changes the type of sequential params
|
||||
|
||||
## 0.13.0
|
||||
## 0.13.0 - 2016-09-06
|
||||
|
||||
* BREAKING CHANGE: `com.rpl.specter.macros` namespace removed and all macros moved into core `com.rpl.specter` namespace
|
||||
* BREAKING CHANGE: Core protocol `Navigator` changed to `RichNavigator` and functions now have an extra argument.
|
||||
|
|
@ -94,7 +142,7 @@
|
|||
* Bug fix: ALL and MAP-VALS transforms on PersistentArrayMap above the threshold now output PersistentArrayMap instead of PersistentHashMap
|
||||
|
||||
|
||||
## 0.12.0
|
||||
## 0.12.0 - 2016-08-05
|
||||
|
||||
* BREAKING CHANGE: Changed semantics of `Navigator` protocol `select*` in order to enable very large performance improvements to `select`, `select-one`, `select-first`, and `select-one!`. Custom navigators will need to be updated to conform to the new required semantics. Codebases that do not use custom navigators do not require any changes. See the docstring on the protocol for the details.
|
||||
* Added `select-any` operation which selects a single element navigated to by the path. Which element returned is undefined. If no elements are navigated to, returns `com.rpl.specter/NONE`. This is the fastest selection operation.
|
||||
|
|
@ -116,11 +164,13 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Bug fix: Using value collection along with `setval` no longer throws exception
|
||||
* Bug fix: Fix error when trying to use Specter along with AOT compilation
|
||||
|
||||
## 0.11.2
|
||||
## 0.11.2 - 2016-06-09
|
||||
|
||||
* Renamed com.rpl.specter.transient namespace to com.rpl.specter.transients to eliminate ClojureScript compiler warning about reserved keyword
|
||||
* Eliminated compiler warnings for ClojureScript version
|
||||
|
||||
## 0.11.1
|
||||
## 0.11.1 - 2016-06-08
|
||||
|
||||
* More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases
|
||||
* Added navigators for transients in com.rpl.specter.transient namespace (thanks @aengelberg)
|
||||
* Huge performance improvement for ALL transform on maps and vectors
|
||||
|
|
@ -134,7 +184,8 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Bug fix: Fixed nil->val to not replace the val on `false`
|
||||
* Bug fix: Eliminate reflection when using primitive parameters in an inline cached path
|
||||
|
||||
## 0.11.0
|
||||
## 0.11.0 - 2016-05-31
|
||||
|
||||
* New `path` macro does intelligent inline caching of the provided path. The path is factored into a static portion and into params which may change on each usage of the path (e.g. local parameters). The static part is factored and compiled on the first run-through, and then re-used for all subsequent invocations. As an example, `[ALL (keypath k)]` is factored into `[ALL keypath]`, which is compiled and cached, and `[k]`, which is provided on each execution. If it is not possible to precompile the path (e.g. [ALL some-local-variable]), nothing is cached and the path will be compiled on each run-through.
|
||||
* BREAKING CHANGE: all `select/transform/setval/replace-in` functions changed to macros and moved to com.rpl.specter.macros namespace. The new macros now automatically wrap the provided path in `path` to enable inline caching. Expect up to a 100x performance improvement without using explicit precompilation, and to be within 2% to 15% of the performance of explicitly precompiled usage.
|
||||
* Added `select*/transform*/setval*/replace-in*/etc.` functions that have the same functionality as the old `select/transform/setval/replace-in` functions.
|
||||
|
|
@ -148,7 +199,8 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Added "navigator constructors" that can be defined via `defnavconstructor`. These allow defining a flexible function to parameterize a defnav, and the function integrates with inline caching for high performance.
|
||||
|
||||
|
||||
## 0.10.0
|
||||
## 0.10.0 - 2016-04-26
|
||||
|
||||
* Make codebase bootstrap cljs compatible
|
||||
* Remove usage of reducers in cljs version in favor of transducers (thanks @StephenRudolph)
|
||||
* ALL now maintains type of queues (thanks @StephenRudolph)
|
||||
|
|
@ -158,7 +210,8 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Fix filterer to maintain the type of the input sequence in transforms
|
||||
* Integrated zipper navigation into com.rpl.specter.zipper namespace
|
||||
|
||||
## 0.9.3
|
||||
## 0.9.3 - 2016-04-15
|
||||
|
||||
* Change clojure/clojurescript to provided dependencies
|
||||
* ALL on maps auto-coerces MapEntry to vector, enabling smoother transformation of map keys
|
||||
* declarepath can now be parameterized
|
||||
|
|
@ -166,7 +219,8 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Added convenience syntax for defprotocolpath with no params, e.g. (defprotocolpath foo)
|
||||
* Rename VOID to STOP
|
||||
|
||||
## 0.9.2
|
||||
## 0.9.2 - 2016-01-26
|
||||
|
||||
* Added VOID selector which navigates nowhere
|
||||
* Better syntax checking for defpath
|
||||
* Fixed bug in protocol paths (#48)
|
||||
|
|
@ -177,33 +231,39 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Added declarepath and providepath, which enable arbitrary recursive or mutually recursive paths
|
||||
* Renamed paramspath to path
|
||||
|
||||
## 0.9.1
|
||||
## 0.9.1 - 2016-01-05
|
||||
|
||||
* Fixed reflection in protocol path code
|
||||
* Optimized late-bound parameterization for JVM implementation by directly creating the object array rather than use object-array
|
||||
* Incorrectly specified function names in defpath will now throw error
|
||||
|
||||
## 0.9.0
|
||||
## 0.9.0 - 2015-12-12
|
||||
|
||||
* Fixed bug where comp-paths wouldn't work on lazy seqs in cljs
|
||||
* Renamed defparamspath and defparamscollector to defpath and defcollector
|
||||
* For Clojure version only, implemented protocol paths (see #38)
|
||||
|
||||
## 0.8.0
|
||||
## 0.8.0 - 2015-10-10
|
||||
|
||||
* Now compatible with Clojure 1.6.0 and 1.5.1 by switching build to cljx (thanks @MerelyAPseudonym)
|
||||
* Added subset selector (like srange but for sets)
|
||||
* Added nil->val, NIL->SET, NIL->LIST, and NIL->VECTOR selectors to make it easier to manipulate maps (e.g. (setval [:akey NIL->VECTOR END] [:a :b] amap) to append that vector into that value for the map, even if nothing was at that value at the start)
|
||||
|
||||
## 0.7.1
|
||||
## 0.7.1 - 2015-09-24
|
||||
|
||||
* view can now be late-bound parameterized
|
||||
* Added a late-bound parameterized version of using a function as a selector called "pred"
|
||||
* Added paramsfn helper macro for defining filter functions that take late-bound parameters
|
||||
* walker and codewalker can now be late-bound parameterized
|
||||
|
||||
## 0.7.0
|
||||
## 0.7.0 - 2015-09-11
|
||||
|
||||
* Added late-bound parameterization feature: allows selectors that require params to be precompiled without the parameters, and the parameters are supplied later in bulk. This effectively enables Specter to be used in any situation with very high performance.
|
||||
* Converted Specter built-in selectors to use late-bound parameterization when appropriate
|
||||
* ALL, FIRST, and LAST are now precompiled
|
||||
|
||||
## 0.6.2
|
||||
## 0.6.2 - 2015-07-03
|
||||
|
||||
* Added not-selected? selector
|
||||
* Added transformed selector
|
||||
* Sped up CLJS implementation for comp-paths by replacing obj-extends? call with satisfies?
|
||||
|
|
@ -211,33 +271,42 @@ transformations with `transform` one after another when the transformations shar
|
|||
* Used not-native hint to enable direct method invocation to speed up CLJS implementation
|
||||
|
||||
|
||||
## 0.6.1
|
||||
## 0.6.1 - 2015-07-01
|
||||
|
||||
* Huge speedup to ClojureScript implementation by optimizing field access
|
||||
|
||||
## 0.6.0
|
||||
## 0.6.0 - 2015-07-01
|
||||
|
||||
* Added ClojureScript compatibility
|
||||
|
||||
## 0.5.7
|
||||
## 0.5.7 - 2015-06-30
|
||||
|
||||
* Fix bug in select-one! which wouldn't allow nil result
|
||||
|
||||
## 0.5.6
|
||||
## 0.5.6 - 2015-06-29
|
||||
|
||||
* Add multi-path implementation
|
||||
* change FIRST/LAST to select nothing on an empty sequence
|
||||
* Allow sets to be used directly as selectors (acts as filter)
|
||||
|
||||
## 0.5.5
|
||||
## 0.5.5 - 2015-06-22
|
||||
|
||||
* Change filterer to accept a selector (that acts like selected? to determine whether or not to select value)
|
||||
|
||||
## 0.5.4
|
||||
## 0.5.4 - 2015-06-19
|
||||
|
||||
* Change cond-path and if-path to take in a selector for conditionals (same idea as selected?)
|
||||
|
||||
## 0.5.3
|
||||
## 0.5.3 - 2015-06-18
|
||||
|
||||
* Added cond-path and if-path selectors for choosing paths depending on value of structure at that location
|
||||
|
||||
## 0.5.2
|
||||
## 0.5.2 - 2015-06-01
|
||||
|
||||
* Fix error for selectors with one element defined using comp-paths, e.g. [:a (comp-paths :b)]
|
||||
|
||||
## 0.5.1
|
||||
## 0.5.1 - 2015-05-31
|
||||
|
||||
* Added putval for adding external values to collected values list
|
||||
* nil is now interpreted as identity selector
|
||||
* empty selector is now interpreted as identity selector instead of producing error
|
||||
|
|
|
|||
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Contributing
|
||||
|
||||
We welcome external contributions to this project. For ideas for new functionality, we recommend first opening an issue so we can discuss whether it makes sense for the project.
|
||||
|
||||
## Contribution process
|
||||
|
||||
1. Open a pull request. If possible, please include thorough tests of the new code.
|
||||
2. If you have not already signed a contributor agreement, we will request that you sign one. We use Adobe Sign for this so it's very quick and easy. Note that we cannot look at your pull request until a contributor agreement is signed.
|
||||
3. We will then review your pull request and possibly ask for changes.
|
||||
15
DEVELOPER.md
15
DEVELOPER.md
|
|
@ -1,7 +1,7 @@
|
|||
# Running Clojure tests
|
||||
|
||||
```
|
||||
lein cleantest
|
||||
lein do clean, test
|
||||
```
|
||||
|
||||
# Running ClojureScript tests
|
||||
|
|
@ -10,3 +10,16 @@ lein cleantest
|
|||
lein javac
|
||||
lein doo node test-build once
|
||||
```
|
||||
|
||||
# Running benchmarks
|
||||
## All benchmarks
|
||||
```
|
||||
scripts/run-benchmarks
|
||||
```
|
||||
## Individual benchmark(s)
|
||||
Specify the benchmark names as command line args. They will likely each need quoted because they contain spaces.
|
||||
Order is ignored.
|
||||
```
|
||||
scripts/run-benchmarks "prepend to a vector" "filter a sequence"
|
||||
```
|
||||
|
||||
|
|
|
|||
73
README.md
73
README.md
|
|
@ -1,8 +1,12 @@
|
|||
# Specter [](http://travis-ci.org/nathanmarz/specter)
|
||||
# Specter
|
||||
|
||||
Clojure has fantastic facilities for doing immutable programming, with a rich library of persistent data structures and efficient mechanisms for manipulating and traversing them. However, Clojure's story is incomplete. Once you nest data structures – which is extremely common – Clojure becomes cumbersome and complex. Clojure even lacks a facility for a basic task like transforming every value in a generic sequence without changing the type or order of that sequence.
|
||||
Specter rejects Clojure's restrictive approach to immutable data structure manipulation, instead exposing an elegant API to allow any sort of manipulation imaginable. Specter especially excels at querying and transforming nested and recursive data, important use cases that are very complex to handle with vanilla Clojure.
|
||||
|
||||
Specter, available for both Clojure and ClojureScript, provides a high performance abstraction called navigators which complete the story around immutable programming and make it easy to transform and query nested data structures. It allows you to concisely specify what you want to change within a data structure, and get a new data structure back with only your changes applied – everything else is reconstructed and the types of data structures throughout don't unexpectedly change.
|
||||
Specter has an extremely simple core, just a single abstraction called "navigator". Queries and transforms are done by composing navigators into a "path" precisely targeting what you want to retrieve or change. Navigators can be composed with any other navigators, allowing sophisticated manipulations to be expressed very concisely.
|
||||
|
||||
In addition, Specter has performance rivaling hand-optimized code (see [this benchmark](https://gist.github.com/nathanmarz/b7c612b417647db80b9eaab618ff8d83)). Clojure's only comparable built-in operations are `get-in` and `update-in`, and the Specter equivalents are 30% and 85% faster respectively (while being just as concise). Under the hood, Specter uses [advanced dynamic techniques](https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation) to strip away the overhead of composition.
|
||||
|
||||
There are some key differences between the Clojure approach to data manipulation and the Specter approach. Unlike Clojure, Specter always uses the most efficient method possible to implement an operation for a datatype (e.g. `last` vs. `LAST`). Clojure intentionally leaves out many operations, such as prepending to a vector or inserting into the middle of a sequence. Specter has navigators that cover these use cases (`BEFORE-ELEM` and `before-index`) and many more. Finally, Specter transforms always target precise parts of a data structure, leaving everything else the same. For instance, `ALL` targets every value within a sequence, and the resulting transform is always the same type as the input (e.g. a vector stays a vector, a sorted map stays a sorted map).
|
||||
|
||||
Consider these examples:
|
||||
|
||||
|
|
@ -15,7 +19,7 @@ Consider these examples:
|
|||
|
||||
;; Manual Clojure
|
||||
(defn map-vals [m afn]
|
||||
(->> m (map (fn [[k v]] [k (afn v)])) (into {})))
|
||||
(->> m (map (fn [[k v]] [k (afn v)])) (into (empty m))))
|
||||
|
||||
(map-vals data
|
||||
(fn [v]
|
||||
|
|
@ -67,10 +71,6 @@ Consider these examples:
|
|||
```
|
||||
|
||||
|
||||
Specter has performance rivaling hand-optimized code (see [this benchmark](https://gist.github.com/nathanmarz/b7c612b417647db80b9eaab618ff8d83)). Clojure's only comparable built-in operations are `get-in` and `update-in`, and the Specter equivalents are 30% and 85% faster respectively (while being just as concise). Under the hood, Specter uses [advanced dynamic techniques](https://github.com/nathanmarz/specter/wiki/Specter's-inline-caching-implementation) to strip away the overhead of composition. Additionally, the built-in navigators use the most efficient means possible of accessing data structures. For example, `ALL` uses `mapv` on vectors, the `IMapIterable` interface on small maps, and `reduce-kv` in conjunction with transients on larger maps.
|
||||
|
||||
The most important aspect of Specter is its composability. Specter navigators can be composed with any other navigators, so the supported use cases grow combinatorially. And because Specter is completely extensible, it can be used to navigate any data structure or object you have.
|
||||
|
||||
|
||||
# Latest Version
|
||||
|
||||
|
|
@ -83,20 +83,24 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org
|
|||
- Introductory blog post: [Clojure's missing piece](http://nathanmarz.com/blog/clojures-missing-piece.html)
|
||||
- Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk)
|
||||
- Note that this presentation was given before Specter's inline compilation/caching system was developed. You no longer need to do anything special to get near-optimal performance.
|
||||
- List of navigators with examples: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples.
|
||||
- Core operations and defining new navigators: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Macros) provides a more comprehensive overview than the API docs of the core select/transform/etc. operations and the operations for defining new navigators.
|
||||
- [API docs](http://nathanmarz.github.io/specter/)
|
||||
- Performance guide: [This post](https://github.com/nathanmarz/specter/wiki/Specter's-inline-caching-implementation) provides an overview of how Specter achieves its performance.
|
||||
- Screencast on Specter: [Understanding Specter](https://www.youtube.com/watch?v=rh5J4vacG98)
|
||||
- List of navigators with examples: [This wiki page](https://github.com/redplanetlabs/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples.
|
||||
- Core operations and defining new navigators: [This wiki page](https://github.com/redplanetlabs/specter/wiki/List-of-Macros) provides a more comprehensive overview than the API docs of the core select/transform/etc. operations and the operations for defining new navigators.
|
||||
- [This wiki page](https://github.com/redplanetlabs/specter/wiki/Using-Specter-Recursively) explains how to do precise and efficient recursive navigation with Specter.
|
||||
- [This wiki page](https://github.com/redplanetlabs/specter/wiki/Using-Specter-With-Zippers) provides a comprehensive overview of how to use Specter's zipper navigators. Zippers are a much slower navigation method but can perform certain tasks that are not possible with Specter's regular navigators. Note that zippers are rarely needed.
|
||||
- [Cheat Sheet](https://github.com/redplanetlabs/specter/wiki/Cheat-Sheet)
|
||||
- [API docs](http://redplanetlabs.github.io/specter/)
|
||||
- Performance guide: [This post](https://github.com/redplanetlabs/specter/wiki/Specter's-inline-caching-implementation) provides an overview of how Specter achieves its performance.
|
||||
|
||||
Specter's API is contained in these files:
|
||||
|
||||
- [specter.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljc): This contains the built-in navigators and the definition of the core operations.
|
||||
- [transients.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections.
|
||||
- [zipper.cljc](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
|
||||
- [specter.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter.cljc): This contains the built-in navigators and the definition of the core operations.
|
||||
- [transients.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections.
|
||||
- [zipper.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
|
||||
|
||||
# Questions?
|
||||
|
||||
You can ask questions about Specter by [opening an issue](https://github.com/nathanmarz/specter/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquestion+) on Github.
|
||||
You can ask questions about Specter by [opening an issue](https://github.com/redplanetlabs/specter/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aquestion+) on Github.
|
||||
|
||||
You can also find help in the #specter channel on [Clojurians](http://clojurians.net/).
|
||||
|
||||
|
|
@ -143,6 +147,20 @@ user> (setval [:a ALL nil?] NONE {:a [1 2 nil 3 nil]})
|
|||
{:a [1 2 3]}
|
||||
```
|
||||
|
||||
Remove key/value pair from nested map:
|
||||
|
||||
```clojure
|
||||
user> (setval [:a :b :c] NONE {:a {:b {:c 1}}})
|
||||
{:a {:b {}}}
|
||||
```
|
||||
|
||||
Remove key/value pair from nested map, removing maps that become empty along the way:
|
||||
|
||||
```clojure
|
||||
user> (setval [:a (compact :b :c)] NONE {:a {:b {:c 1}}})
|
||||
{}
|
||||
```
|
||||
|
||||
Increment all the odd numbers between indices 1 (inclusive) and 4 (exclusive):
|
||||
|
||||
```clojure
|
||||
|
|
@ -172,10 +190,10 @@ user> (select (walker number?)
|
|||
[2 1 2 1 2 6 7 4]
|
||||
```
|
||||
|
||||
Navigate via non-keyword keys:
|
||||
Navigate with string keys:
|
||||
|
||||
```clojure
|
||||
user> (select (keypath "a" "b")
|
||||
user> (select ["a" "b"]
|
||||
{"a" {"b" 10}})
|
||||
[10]
|
||||
```
|
||||
|
|
@ -303,8 +321,23 @@ Specter supports ClojureScript! However, some of the differences between Clojure
|
|||
|
||||
# Future work
|
||||
- Integrate Specter with other kinds of data structures, such as graphs. Desired navigations include: reduction in topological order, navigate to outgoing/incoming nodes, to a subgraph (with metadata indicating how to attach external edges on transformation), to node attributes, to node values, to specific nodes.
|
||||
- Make it possible to parallelize selects/transforms
|
||||
|
||||
# clj-kondo
|
||||
|
||||
When using Specter in a project with [clj-kondo](https://github.com/clj-kondo/clj-kondo), a lot of the vars will be considered unresolved because internally Specter defines them with macros. The following configuration snippet will resolve these issues if you include it in your `.clj-kondo/config.edn` file.
|
||||
|
||||
```clojure
|
||||
{:lint-as {com.rpl.specter/defcollector clojure.core/defn
|
||||
com.rpl.specter/defdynamicnav clojure.core/defn
|
||||
com.rpl.specter/defmacroalias clojure.core/def
|
||||
com.rpl.specter/defnav clojure.core/defn
|
||||
com.rpl.specter/defrichnav clojure.core/defn}}
|
||||
```
|
||||
|
||||
# Babashka
|
||||
|
||||
This library is compatible with [babashka](https://babashka.org/) as of specter 1.1.4 and babashka 0.7.8.
|
||||
|
||||
# License
|
||||
|
||||
Copyright 2015-2017 Red Planet Labs, Inc. Specter is licensed under Apache License v2.0.
|
||||
Copyright 2015-2020 Red Planet Labs, Inc. Specter is licensed under Apache License v2.0.
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
1.0.3-SNAPSHOT
|
||||
1.1.5-SNAPSHOT
|
||||
|
|
|
|||
19
bb.edn
Normal file
19
bb.edn
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{:paths ["src/clj"]
|
||||
:tasks
|
||||
{test:clj {:doc "Run clj tests with leiningen"
|
||||
:task (shell "lein test")}
|
||||
|
||||
test:cljs {:doc "Run cljs tests with leiningen"
|
||||
:task (shell "lein doo node test-build once")}
|
||||
|
||||
test:bb {:doc "Run bb tests"
|
||||
:extra-paths ["test"]
|
||||
:extra-deps {org.clojure/test.check {:mvn/version "0.9.0"}
|
||||
io.github.cognitect-labs/test-runner
|
||||
{:git/tag "v0.5.0" :git/sha "b3fd0d2"}
|
||||
org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace"
|
||||
:git/sha "3625153ee66dfcec2ba600851b5b2cbdab8fae6c"}}
|
||||
:requires ([cognitect.test-runner :as tr])
|
||||
:task (apply tr/-main
|
||||
"-d" "test"
|
||||
*command-line-args*)}}}
|
||||
13
project.clj
13
project.clj
|
|
@ -9,7 +9,7 @@
|
|||
:test-paths ["test", "target/test-classes"]
|
||||
:auto-clean false
|
||||
:dependencies [[riddley "0.1.12"]]
|
||||
:plugins [[lein-codox "0.9.5"]
|
||||
:plugins [[lein-codox "0.10.7"]
|
||||
[lein-doo "0.1.7"]]
|
||||
:codox {:source-paths ["target/classes" "src/clj"]
|
||||
:namespaces [com.rpl.specter
|
||||
|
|
@ -30,9 +30,14 @@
|
|||
|
||||
:profiles {:dev {:dependencies
|
||||
[[org.clojure/test.check "0.9.0"]
|
||||
[org.clojure/clojure "1.8.0"]
|
||||
[org.clojure/clojurescript "1.9.229"]]}
|
||||
|
||||
[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/clojurescript "1.10.439"]]}
|
||||
:bench {:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[criterium "0.4.4"]]}
|
||||
:test {:dependencies [[org.clojure/clojure "1.7.0"]]}}
|
||||
|
||||
:deploy-repositories
|
||||
[["clojars" {:url "https://repo.clojars.org"
|
||||
:sign-releases false}]]
|
||||
|
||||
:aliases {"deploy" ["do" "clean," "deploy" "clojars"]})
|
||||
|
|
|
|||
|
|
@ -2,57 +2,31 @@
|
|||
(:use [com.rpl.specter]
|
||||
[com.rpl.specter.transients])
|
||||
(:require [clojure.walk :as walk]
|
||||
[com.rpl.specter.impl :as i]))
|
||||
|
||||
|
||||
;; run via `lein repl` with `(load-file "scripts/benchmarks.clj")`
|
||||
|
||||
|
||||
(defn pretty-float5 [anum]
|
||||
(format "%.5g" anum))
|
||||
[com.rpl.specter.impl :as i]
|
||||
[criterium.core :as bench]))
|
||||
|
||||
(defn pretty-float3 [anum]
|
||||
(format "%.3g" anum))
|
||||
|
||||
(defn time-ms [amt afn]
|
||||
(let [start (System/nanoTime)
|
||||
_ (dotimes [_ amt] (afn))
|
||||
end (System/nanoTime)]
|
||||
(/ (- end start) 1000000.0)))
|
||||
(defn mean [a-fn]
|
||||
(-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
|
||||
|
||||
|
||||
(defn avg [numbers]
|
||||
(/ (reduce + numbers)
|
||||
(count numbers)
|
||||
1.0))
|
||||
|
||||
(defn average-time-ms [iters amt-per-iter afn]
|
||||
(avg
|
||||
;; treat 1st run as warmup
|
||||
(next
|
||||
(for [i (range (inc iters))]
|
||||
(time-ms amt-per-iter afn)))))
|
||||
|
||||
(defn compare-benchmark [amt-per-iter afn-map]
|
||||
(System/runFinalization)
|
||||
(System/gc)
|
||||
(let [results (transform MAP-VALS
|
||||
(fn [afn]
|
||||
(average-time-ms 8 amt-per-iter afn))
|
||||
afn-map)
|
||||
(defn compare-benchmark [afn-map]
|
||||
(let [results (transform MAP-VALS mean afn-map)
|
||||
[[_ best-time] & _ :as sorted] (sort-by last results)]
|
||||
|
||||
(println "\nAvg(ms)\t\tvs best\t\tCode")
|
||||
(println "\nMean(us)\tvs best\t\tCode")
|
||||
(doseq [[k t] sorted]
|
||||
(println (pretty-float5 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
|
||||
(println (pretty-float3 t) "\t\t" (pretty-float3 (/ t best-time 1.0)) "\t\t" k))))
|
||||
|
||||
|
||||
(defmacro run-benchmark [name amt-per-iter & exprs]
|
||||
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
|
||||
`(do
|
||||
(println "Benchmark:" ~name (str "(" ~amt-per-iter " iterations)"))
|
||||
(compare-benchmark ~amt-per-iter ~afn-map)
|
||||
(println "\n********************************\n"))))
|
||||
(defmacro run-benchmark [name & exprs]
|
||||
(let [only-benchmarks (set (filter some? *command-line-args*))
|
||||
all-benchmarks? (empty? only-benchmarks)]
|
||||
(if (or all-benchmarks? (contains? only-benchmarks name))
|
||||
(let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
|
||||
`(do
|
||||
(println "Benchmark:" ~name)
|
||||
(compare-benchmark ~afn-map)
|
||||
(println "\n********************************\n"))))))
|
||||
|
||||
(defn specter-dynamic-nested-get [data a b c]
|
||||
(select-any (keypath a b c) data))
|
||||
|
|
@ -69,7 +43,7 @@
|
|||
|
||||
(let [data {:a {:b {:c 1}}}
|
||||
p (comp-paths :a :b :c)]
|
||||
(run-benchmark "get value in nested map" 2500000
|
||||
(run-benchmark "get value in nested map"
|
||||
(select-any [:a :b :c] data)
|
||||
(select-any (keypath :a :b :c) data)
|
||||
(select-one [:a :b :c] data)
|
||||
|
|
@ -86,7 +60,7 @@
|
|||
|
||||
|
||||
(let [data {:a {:b {:c 1}}}]
|
||||
(run-benchmark "set value in nested map" 2500000
|
||||
(run-benchmark "set value in nested map"
|
||||
(assoc-in data [:a :b :c] 1)
|
||||
(setval [:a :b :c] 1 data)))
|
||||
|
||||
|
|
@ -103,7 +77,7 @@
|
|||
(my-update m3 :c afn))))))
|
||||
|
||||
(let [data {:a {:b {:c 1}}}]
|
||||
(run-benchmark "update value in nested map" 500000
|
||||
(run-benchmark "update value in nested map"
|
||||
(update-in data [:a :b :c] inc)
|
||||
(transform [:a :b :c] inc data)
|
||||
(manual-transform data inc)))
|
||||
|
|
@ -135,14 +109,14 @@
|
|||
|
||||
|
||||
(let [data '(1 2 3 4 5)]
|
||||
(run-benchmark "transform values of a list" 500000
|
||||
(run-benchmark "transform values of a list"
|
||||
(transform ALL inc data)
|
||||
(doall (sequence (map inc) data))
|
||||
(reverse (into '() (map inc) data))
|
||||
))
|
||||
|
||||
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
||||
(run-benchmark "transform values of a small map" 500000
|
||||
(run-benchmark "transform values of a small map"
|
||||
(into {} (for [[k v] data] [k (inc v)]))
|
||||
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
|
||||
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
|
||||
|
|
@ -158,7 +132,7 @@
|
|||
|
||||
|
||||
(let [data (->> (for [i (range 1000)] [i i]) (into {}))]
|
||||
(run-benchmark "transform values of large map" 600
|
||||
(run-benchmark "transform values of large map"
|
||||
(into {} (for [[k v] data] [k (inc v)]))
|
||||
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data)
|
||||
(persistent! (reduce-kv (fn [m k v] (assoc! m k (inc v))) (transient {}) data))
|
||||
|
|
@ -174,7 +148,7 @@
|
|||
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||
(run-benchmark "first value of a size 10 vector" 10000000
|
||||
(run-benchmark "first value of a size 10 vector"
|
||||
(first data)
|
||||
(select-any ALL data)
|
||||
(select-any FIRST data)
|
||||
|
|
@ -182,7 +156,7 @@
|
|||
))
|
||||
|
||||
(let [data [1 2 3 4 5]]
|
||||
(run-benchmark "map a function over a vector" 1000000
|
||||
(run-benchmark "map a function over a vector"
|
||||
(vec (map inc data))
|
||||
(mapv inc data)
|
||||
(transform ALL inc data)
|
||||
|
|
@ -190,7 +164,7 @@
|
|||
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||
(run-benchmark "filter a sequence" 500000
|
||||
(run-benchmark "filter a sequence"
|
||||
(doall (filter even? data))
|
||||
(filterv even? data)
|
||||
(select [ALL even?] data)
|
||||
|
|
@ -200,7 +174,7 @@
|
|||
|
||||
(let [data [{:a 2 :b 2} {:a 1} {:a 4} {:a 6}]
|
||||
xf (comp (map :a) (filter even?))]
|
||||
(run-benchmark "even :a values from sequence of maps" 500000
|
||||
(run-benchmark "even :a values from sequence of maps"
|
||||
(select [ALL :a even?] data)
|
||||
(->> data (mapv :a) (filter even?) doall)
|
||||
(into [] (comp (map :a) (filter even?)) data)
|
||||
|
|
@ -209,14 +183,13 @@
|
|||
|
||||
(let [v (vec (range 1000))]
|
||||
(run-benchmark "Append to a large vector"
|
||||
2000000
|
||||
(setval END [1] v)
|
||||
(setval AFTER-ELEM 1 v)
|
||||
(reduce conj v [1])
|
||||
(conj v 1)))
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9 10]]
|
||||
(run-benchmark "prepend to a vector" 1000000
|
||||
(run-benchmark "prepend to a vector"
|
||||
(vec (cons 0 data))
|
||||
(setval BEFORE-ELEM 0 data)
|
||||
(into [0] data)
|
||||
|
|
@ -244,7 +217,6 @@
|
|||
|
||||
(let [data [1 2 [[3]] [4 6 [7 [8]] 10]]]
|
||||
(run-benchmark "update every value in a tree (represented with vectors)"
|
||||
50000
|
||||
(walk/postwalk (fn [e] (if (and (number? e) (even? e)) (inc e) e)) data)
|
||||
(transform [(walker number?) even?] inc data)
|
||||
(transform [TreeValues even?] inc data)
|
||||
|
|
@ -254,7 +226,6 @@
|
|||
|
||||
(let [toappend (range 1000)]
|
||||
(run-benchmark "transient comparison: building up vectors"
|
||||
8000
|
||||
(reduce (fn [v i] (conj v i)) [] toappend)
|
||||
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
||||
(setval END toappend [])
|
||||
|
|
@ -262,7 +233,6 @@
|
|||
|
||||
(let [toappend (range 1000)]
|
||||
(run-benchmark "transient comparison: building up vectors one at a time"
|
||||
7000
|
||||
(reduce (fn [v i] (conj v i)) [] toappend)
|
||||
(reduce (fn [v i] (conj! v i)) (transient []) toappend)
|
||||
(reduce (fn [v i] (setval END [i] v)) [] toappend)
|
||||
|
|
@ -273,7 +243,6 @@
|
|||
tdata (transient data)
|
||||
tdata2 (transient data)]
|
||||
(run-benchmark "transient comparison: assoc'ing in vectors"
|
||||
2500000
|
||||
(assoc data 600 0)
|
||||
(assoc! tdata 600 0)
|
||||
(setval (keypath 600) 0 data)
|
||||
|
|
@ -284,7 +253,6 @@
|
|||
tdata (transient data)
|
||||
tdata2 (transient data)]
|
||||
(run-benchmark "transient comparison: assoc'ing in maps"
|
||||
1500000
|
||||
(assoc data 600 0)
|
||||
(assoc! tdata 600 0)
|
||||
(setval (keypath 600) 0 data)
|
||||
|
|
@ -298,31 +266,27 @@
|
|||
[k (rand)]))
|
||||
tdata (transient data)]
|
||||
(run-benchmark "transient comparison: submap"
|
||||
150000
|
||||
(transform (submap [600 700]) modify-submap data)
|
||||
(transform (submap! [600 700]) modify-submap tdata)))
|
||||
|
||||
(let [data {:x 1}
|
||||
meta-map {:my :metadata}]
|
||||
(run-benchmark "set metadata"
|
||||
1500000
|
||||
(with-meta data meta-map)
|
||||
(setval META meta-map data)))
|
||||
|
||||
(let [data (with-meta {:x 1} {:my :metadata})]
|
||||
(run-benchmark "get metadata"
|
||||
15000000
|
||||
(meta data)
|
||||
(select-any META data)))
|
||||
|
||||
(let [data (with-meta {:x 1} {:my :metadata})]
|
||||
(run-benchmark "vary metadata"
|
||||
800000
|
||||
(vary-meta data assoc :y 2)
|
||||
(setval [META :y] 2 data)))
|
||||
|
||||
(let [data (range 1000)]
|
||||
(run-benchmark "Traverse into a set" 5000
|
||||
(run-benchmark "Traverse into a set"
|
||||
(set data)
|
||||
(set (select ALL data))
|
||||
(into #{} (traverse ALL data))
|
||||
|
|
@ -334,18 +298,18 @@
|
|||
(defn mult-10 [v] (* 10 v))
|
||||
|
||||
(let [data [1 2 3 4 5 6 7 8 9]]
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav" 300000
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, one shared nav"
|
||||
(->> data (transform [ALL even?] mult-10) (transform [ALL odd?] dec))
|
||||
(multi-transform [ALL (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
|
||||
|
||||
|
||||
(let [data [[1 2 3 4 :a] [5] [6 7 :b 8 9] [10 11 12 13]]]
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs" 150000
|
||||
(run-benchmark "multi-transform vs. consecutive transforms, three shared navs"
|
||||
(->> data (transform [ALL ALL number? even?] mult-10) (transform [ALL ALL number? odd?] dec))
|
||||
(multi-transform [ALL ALL number? (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data)))
|
||||
|
||||
(let [data {:a 1 :b 2 :c 3 :d 4}]
|
||||
(run-benchmark "namespace qualify keys of a small map" 1000000
|
||||
(run-benchmark "namespace qualify keys of a small map"
|
||||
(into {}
|
||||
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
|
||||
data)
|
||||
|
|
@ -355,7 +319,7 @@
|
|||
|
||||
|
||||
(let [data (->> (for [i (range 1000)] [(keyword (str i)) i]) (into {}))]
|
||||
(run-benchmark "namespace qualify keys of a large map" 1200
|
||||
(run-benchmark "namespace qualify keys of a large map"
|
||||
(into {}
|
||||
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
|
||||
data)
|
||||
|
|
@ -370,7 +334,24 @@
|
|||
(i/walk-until afn next-fn structure)))
|
||||
|
||||
(let [data {:a [1 2 {:c '(3 4) :d {:e [1 2 3] 7 8 9 10}}]}]
|
||||
(run-benchmark "walker vs. clojure.walk version" 150000
|
||||
(run-benchmark "walker vs. clojure.walk version"
|
||||
(transform (walker number?) inc data)
|
||||
(transform (walker-old number?) inc data)
|
||||
))
|
||||
|
||||
(let [size 1000
|
||||
middle-idx (/ size 2)
|
||||
v -1
|
||||
rng (range size)
|
||||
data-vec (vec rng)
|
||||
data-lst (apply list rng)]
|
||||
(run-benchmark "before-index vs. srange in middle (vector)"
|
||||
(setval (before-index middle-idx) v data-vec)
|
||||
(setval (srange middle-idx middle-idx) [v] data-vec))
|
||||
(run-benchmark "before-index vs. srange in middle (list)"
|
||||
(setval (before-index middle-idx) v data-lst)
|
||||
(setval (srange middle-idx middle-idx) [v] data-lst))
|
||||
(run-benchmark "before-index at 0 vs. srange vs. cons (list)"
|
||||
(setval (before-index 0) v data-lst)
|
||||
(setval (srange 0 0) [v] data-lst)
|
||||
(cons v data-lst)))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
`lein javac`
|
||||
java -server -XX:MaxPermSize=128m -XX:MaxInlineSize=100 -cp `lein classpath` clojure.main scripts/benchmarks.clj
|
||||
lein javac
|
||||
lein version
|
||||
echo
|
||||
lein show-profiles bench
|
||||
echo
|
||||
java -server -XX:MaxInlineSize=100 -cp "$(lein with-profile bench classpath)" clojure.main scripts/benchmarks.clj "$@"
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@
|
|||
dynamicnav
|
||||
richnav
|
||||
defrichnav
|
||||
recursive-path]]
|
||||
recursive-path
|
||||
select
|
||||
transform
|
||||
setval
|
||||
select-any]]
|
||||
|
||||
[com.rpl.specter.util-macros :refer
|
||||
[doseqres]]))
|
||||
|
|
@ -111,7 +115,32 @@
|
|||
(providepath ~self-sym ~path)
|
||||
~self-sym)))))
|
||||
|
||||
;; copied from tools.macro to avoid the dependency
|
||||
;; copied from clojure.core
|
||||
(def
|
||||
^{:private true}
|
||||
sigs
|
||||
(fn [fdecl]
|
||||
(let [asig
|
||||
(fn [fdecl]
|
||||
(let [arglist (first fdecl)
|
||||
;; elide implicit macro args
|
||||
arglist (if (= '&form (first arglist))
|
||||
(subvec arglist 2 (count arglist))
|
||||
arglist)
|
||||
body (next fdecl)]
|
||||
(if (map? (first body))
|
||||
(if (next body)
|
||||
(with-meta arglist (conj (if (meta arglist) (meta arglist) {}) (first body)))
|
||||
arglist)
|
||||
arglist)))]
|
||||
(if (seq? (first fdecl))
|
||||
(loop [ret [] fdecls fdecl]
|
||||
(if fdecls
|
||||
(recur (conj ret (asig (first fdecls))) (next fdecls))
|
||||
(seq ret)))
|
||||
(list (asig fdecl))))))
|
||||
|
||||
;; partially copied from clojure.core/defn
|
||||
(defn- name-with-attributes
|
||||
"To be used in macro definitions.
|
||||
Handles optional docstrings and attribute maps for a name to be defined
|
||||
|
|
@ -122,20 +151,31 @@
|
|||
macro argument list. The return value is a vector containing the name
|
||||
with its extended metadata map and the list of unprocessed macro
|
||||
arguments."
|
||||
[name macro-args]
|
||||
(let [[docstring macro-args] (if (string? (first macro-args))
|
||||
[(first macro-args) (next macro-args)]
|
||||
[nil macro-args])
|
||||
[attr macro-args] (if (map? (first macro-args))
|
||||
[(first macro-args) (next macro-args)]
|
||||
[{} macro-args])
|
||||
attr (if docstring
|
||||
(assoc attr :doc docstring)
|
||||
attr)
|
||||
attr (if (meta name)
|
||||
(conj (meta name) attr)
|
||||
attr)]
|
||||
[(with-meta name attr) macro-args]))
|
||||
[name fdecl]
|
||||
(let [m (if (string? (first fdecl))
|
||||
{:doc (first fdecl)}
|
||||
{})
|
||||
|
||||
fdecl (if (string? (first fdecl))
|
||||
(next fdecl)
|
||||
fdecl)
|
||||
m (if (map? (first fdecl))
|
||||
(conj m (first fdecl))
|
||||
m)
|
||||
fdecl (if (map? (first fdecl))
|
||||
(next fdecl)
|
||||
fdecl)
|
||||
fdecl (if (vector? (first fdecl))
|
||||
(list fdecl)
|
||||
fdecl)
|
||||
m (if (map? (last fdecl))
|
||||
(conj m (last fdecl))
|
||||
m)
|
||||
fdecl (if (map? (last fdecl))
|
||||
(butlast fdecl)
|
||||
fdecl)
|
||||
m (conj {:arglists (list 'quote (sigs fdecl))} m)]
|
||||
[(with-meta name m) fdecl]))
|
||||
|
||||
(defmacro dynamicnav [& args]
|
||||
`(vary-meta (wrap-dynamic-nav (fn ~@args)) assoc :dynamicnav true))
|
||||
|
|
@ -162,7 +202,10 @@
|
|||
embed (i/maybe-direct-nav path (-> s meta :direct-nav))]
|
||||
`(com.rpl.specter.impl/->LocalSym ~path (quote ~embed)))
|
||||
;; var-get doesn't work in cljs, so capture the val in the macro instead
|
||||
`(com.rpl.specter.impl/->VarUse ~path (var ~path) (quote ~path)))
|
||||
`(com.rpl.specter.impl/->VarUse
|
||||
~path
|
||||
~(if-not (instance? Class (resolve path)) `(var ~path))
|
||||
(quote ~path)))
|
||||
|
||||
|
||||
(i/fn-invocation? path)
|
||||
|
|
@ -345,10 +388,16 @@
|
|||
[apath transform-fn structure]
|
||||
`(i/compiled-transform* (path ~apath) ~transform-fn ~structure))
|
||||
|
||||
(defmacro vtransform
|
||||
"Navigates to each value specified by the path and replaces it by the result of running
|
||||
the transform-fn on two arguments: the collected values as a vector, and the navigated value."
|
||||
[apath transform-fn structure]
|
||||
`(i/compiled-vtransform* (path ~apath) ~transform-fn ~structure))
|
||||
|
||||
(defmacro multi-transform
|
||||
"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
|
||||
inline in the path using `terminal` or `vterminal`. 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`.
|
||||
This macro will do inline caching of the path."
|
||||
[apath structure]
|
||||
|
|
@ -443,7 +492,8 @@
|
|||
params (-> protpath-prot :sigs first last :arglists first)]
|
||||
(doseq [[atype path-code] extensions]
|
||||
(extend atype protpath-prot
|
||||
{m (eval `(fn ~params (path ~path-code)))}))))
|
||||
{m (binding [*compile-files* false]
|
||||
(eval `(fn ~params (path ~path-code))))}))))
|
||||
|
||||
(defmacro extend-protocolpath
|
||||
"Used in conjunction with `defprotocolpath`. See [[defprotocolpath]]."
|
||||
|
|
@ -548,6 +598,10 @@
|
|||
(def ^{:doc "Version of transform that takes in a path precompiled with comp-paths"}
|
||||
compiled-transform i/compiled-transform*)
|
||||
|
||||
(def ^{:doc "Version of vtransform that takes in a path precompiled with comp-paths"}
|
||||
compiled-vtransform i/compiled-vtransform*)
|
||||
|
||||
|
||||
(defn transform*
|
||||
"Navigates to each value specified by the path and replaces it by the result of running
|
||||
the transform-fn on it"
|
||||
|
|
@ -560,8 +614,8 @@
|
|||
|
||||
(defn multi-transform*
|
||||
"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
|
||||
inline in the path using `terminal` or `vterminal`. 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`."
|
||||
[path structure]
|
||||
(compiled-multi-transform (i/comp-paths* path) structure))
|
||||
|
|
@ -629,17 +683,28 @@
|
|||
i/STAY*)
|
||||
|
||||
(def
|
||||
^{:doc "For usage with `multi-transform`, defines an endpoint in the navigation
|
||||
that will have the parameterized transform function run. The transform
|
||||
^{:doc "Defines an endpoint in the navigation the transform function run. The transform
|
||||
function works just like it does in `transform`, with collected values
|
||||
given as the first arguments"}
|
||||
terminal
|
||||
(richnav [afn]
|
||||
(select* [this vals structure next-fn]
|
||||
(i/throw-illegal "'terminal' should only be used in multi-transform"))
|
||||
NONE)
|
||||
(transform* [this vals structure next-fn]
|
||||
(i/terminal* afn vals structure))))
|
||||
|
||||
(def
|
||||
^{:doc "Defines an endpoint in the navigation the transform function run.The transform
|
||||
function works differently than it does in `transform`. Rather than receive
|
||||
collected vals spliced in as the first arguments to the function, this function
|
||||
always takes two arguemnts. The first is all collected vals in a vector, and
|
||||
the second is the navigated value."}
|
||||
vterminal
|
||||
(richnav [afn]
|
||||
(select* [this vals structure next-fn]
|
||||
NONE)
|
||||
(transform* [this vals structure next-fn]
|
||||
(afn vals structure))))
|
||||
|
||||
(defn ^:direct-nav terminal-val
|
||||
"Like `terminal` but specifies a val to set at the location regardless of
|
||||
|
|
@ -867,7 +932,7 @@
|
|||
(transform* [this structure next-fn]
|
||||
(let [select-result (compiled-select late structure)
|
||||
transformed (next-fn select-result)
|
||||
values-to-insert (i/mutable-cell transformed)]
|
||||
values-to-insert (i/mutable-cell (seq transformed))]
|
||||
(compiled-transform late
|
||||
(fn [_] (let [vs (i/get-cell values-to-insert)]
|
||||
(if vs
|
||||
|
|
@ -931,11 +996,105 @@
|
|||
must
|
||||
(eachnav n/must*))
|
||||
|
||||
(def ^{:doc "Navigate to the specified indices one after another.If navigate to
|
||||
(def ^{:doc "Navigate to the specified indices one after another. If navigate to
|
||||
NONE, that element is removed from the sequence."}
|
||||
nthpath
|
||||
(eachnav n/nthpath*))
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to the empty space between the index and the prior index. For select
|
||||
navigates to NONE, and transforms to non-NONE insert at that position."}
|
||||
before-index
|
||||
[index]
|
||||
(select* [this vals structure next-fn]
|
||||
NONE)
|
||||
(transform* [this vals structure next-fn]
|
||||
(let [v (next-fn vals NONE)]
|
||||
(if
|
||||
(identical? NONE v)
|
||||
structure
|
||||
(n/insert-before-idx structure index v)))))
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to the index of the sequence if within 0 and size. Transforms move element
|
||||
at that index to the new index, shifting other elements in the sequence."}
|
||||
index-nav
|
||||
[i]
|
||||
(select* [this vals structure next-fn]
|
||||
(if (and (>= i 0) (< i (count structure)))
|
||||
(next-fn vals i)
|
||||
NONE
|
||||
))
|
||||
(transform* [this vals structure next-fn]
|
||||
(if (and (>= i 0) (< i (count structure)))
|
||||
(let [newi (next-fn vals i)]
|
||||
(if (= newi i)
|
||||
structure
|
||||
(let [v (nth structure i)]
|
||||
(if (vector? structure)
|
||||
(let [shifted (if (< newi i)
|
||||
(loop [j (dec i)
|
||||
s structure]
|
||||
(if (< j newi)
|
||||
s
|
||||
(recur (dec j) (assoc s (inc j) (nth s j)))
|
||||
))
|
||||
(loop [j (inc i)
|
||||
s structure]
|
||||
(if (> j newi)
|
||||
s
|
||||
(recur (inc j) (assoc s (dec j) (nth s j)))
|
||||
)))]
|
||||
(assoc shifted newi v)
|
||||
)
|
||||
(->> structure
|
||||
(setval (nthpath i) NONE)
|
||||
(setval (before-index newi) v)
|
||||
)))))
|
||||
structure
|
||||
)))
|
||||
|
||||
(defnav
|
||||
^{:doc "Navigate to [index elem] pairs for each element in a sequence. The sequence will be indexed
|
||||
starting from `start`. Changing index in transform has same effect as `index-nav`. Indices seen
|
||||
during transform take into account any shifting from prior sequence elements changing indices."}
|
||||
indexed-vals
|
||||
[start]
|
||||
(select* [this structure next-fn]
|
||||
;; could be more efficient with a primitive mutable field
|
||||
(let [i (i/mutable-cell (dec start))]
|
||||
(doseqres NONE [e structure]
|
||||
(i/update-cell! i inc)
|
||||
(next-fn [(i/get-cell i) e])
|
||||
)))
|
||||
(transform* [this structure next-fn]
|
||||
(let [indices (i/mutable-cell (-> structure count range))]
|
||||
(reduce
|
||||
(fn [s e]
|
||||
(let [curri (first (i/get-cell indices))
|
||||
[newi* newe] (next-fn [(+ start curri) e])
|
||||
newi (- newi* start)]
|
||||
(i/update-cell!
|
||||
indices
|
||||
(fn [ii]
|
||||
(let [ii2 (next ii)]
|
||||
(if (> newi curri)
|
||||
(transform [ALL #(>= % (inc curri)) #(<= % newi)] dec ii2)
|
||||
ii2
|
||||
))))
|
||||
(->> s
|
||||
(setval (nthpath curri) newe)
|
||||
(setval (index-nav curri) newi)
|
||||
)))
|
||||
structure
|
||||
structure
|
||||
))))
|
||||
|
||||
(def
|
||||
^{:doc "`indexed-vals` with a starting index of 0."}
|
||||
INDEXED-VALS
|
||||
(indexed-vals 0))
|
||||
|
||||
(defrichnav
|
||||
^{:doc "Navigates to result of running `afn` on the currently navigated value."}
|
||||
view
|
||||
|
|
@ -969,6 +1128,13 @@
|
|||
(swap! structure next-fn)
|
||||
structure)))
|
||||
|
||||
(defnav regex-nav [re]
|
||||
(select* [this structure next-fn]
|
||||
(doseqres NONE [s (re-seq re structure)]
|
||||
(next-fn s)))
|
||||
(transform* [this structure next-fn]
|
||||
(clojure.string/replace structure re next-fn)))
|
||||
|
||||
(defdynamicnav selected?
|
||||
"Filters the current value based on whether a path finds anything.
|
||||
e.g. (selected? :vals ALL even?) keeps the current element only if an
|
||||
|
|
@ -1042,8 +1208,8 @@
|
|||
)))
|
||||
|
||||
(def
|
||||
^{:doc "Keeps the element only if it matches the supplied predicate. This is the
|
||||
late-bound parameterized version of using a function directly in a path."
|
||||
^{:doc "Keeps the element only if it matches the supplied predicate. Functions in paths
|
||||
implicitly convert to this navigator."
|
||||
:direct-nav true}
|
||||
pred
|
||||
i/pred*)
|
||||
|
|
@ -1063,6 +1229,26 @@
|
|||
ImplicitNav
|
||||
(implicit-nav [this] (n/keypath* this)))
|
||||
|
||||
(extend-type #?(:clj clojure.lang.Symbol :cljs cljs.core/Symbol)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (n/keypath* this)))
|
||||
|
||||
(extend-type #?(:clj String :cljs string)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (n/keypath* this)))
|
||||
|
||||
(extend-type #?(:clj Number :cljs number)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (n/keypath* this)))
|
||||
|
||||
(extend-type #?(:clj Character :cljs char)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (n/keypath* this)))
|
||||
|
||||
(extend-type #?(:clj Boolean :cljs boolean)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (n/keypath* this)))
|
||||
|
||||
(extend-type #?(:clj clojure.lang.AFn :cljs function)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (pred this)))
|
||||
|
|
@ -1071,6 +1257,10 @@
|
|||
ImplicitNav
|
||||
(implicit-nav [this] (pred this)))
|
||||
|
||||
(extend-type #?(:clj java.util.regex.Pattern :cljs js/RegExp)
|
||||
ImplicitNav
|
||||
(implicit-nav [this] (regex-nav this)))
|
||||
|
||||
(defnav
|
||||
^{:doc "Navigates to the provided val if the structure is nil. Otherwise it stays
|
||||
navigated at the structure."}
|
||||
|
|
@ -1118,7 +1308,7 @@
|
|||
ns (namespace structure)]
|
||||
(cond (keyword? structure) (keyword ns new-name)
|
||||
(symbol? structure) (symbol ns new-name)
|
||||
:else (i/throw-illegal "NAME can only be used on symbols or keywords - " structure)
|
||||
:else (throw (ex-info "NAME can only be used on symbols or keywords" {:structure structure}))
|
||||
))))
|
||||
|
||||
(defnav ^{:doc "Navigates to the namespace portion of the keyword or symbol"}
|
||||
|
|
@ -1131,7 +1321,8 @@
|
|||
new-ns (next-fn (namespace structure))]
|
||||
(cond (keyword? structure) (keyword new-ns name)
|
||||
(symbol? structure) (symbol new-ns name)
|
||||
:else (i/throw-illegal "NAMESPACE can only be used on symbols or keywords - " structure)
|
||||
:else (throw (ex-info "NAMESPACE can only be used on symbols or keywords"
|
||||
{:structure structure}))
|
||||
))))
|
||||
|
||||
(defdynamicnav
|
||||
|
|
@ -1241,11 +1432,7 @@
|
|||
Tests the structure if selecting with cond-path returns anything.
|
||||
If so, it uses the following path for this portion of the navigation.
|
||||
Otherwise, it tries the next cond-path. If nothing matches, then the structure
|
||||
is not selected.
|
||||
|
||||
The input paths may be parameterized, in which case the result of cond-path
|
||||
will be parameterized in the order of which the parameterized navigators
|
||||
were declared."
|
||||
is not selected."
|
||||
[& conds]
|
||||
(let [pairs (reverse (partition 2 conds))]
|
||||
(reduce
|
||||
|
|
@ -1308,3 +1495,12 @@
|
|||
(cond-path (pred afn) STAY
|
||||
coll? [ALL-WITH-META p]
|
||||
)))
|
||||
|
||||
(let [empty->NONE (if-path empty? (terminal-val NONE))
|
||||
compact* (fn [nav] (multi-path nav empty->NONE))]
|
||||
(defdynamicnav compact
|
||||
"During transforms, after each step of navigation in subpath check if the
|
||||
value is empty. If so, remove that value by setting it to NONE."
|
||||
[& path]
|
||||
(map compact* path)
|
||||
))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
(ns com.rpl.specter.impl
|
||||
#?(:cljs (:require-macros
|
||||
[com.rpl.specter.util-macros
|
||||
:refer [doseqres mk-comp-navs mk-late-fn mk-late-fn-records]]))
|
||||
:refer [doseqres mk-comp-navs mk-late-fn mk-late-fn-records]]
|
||||
))
|
||||
;; workaround for cljs bug that emits warnings for vars named the same as a
|
||||
;; private var in cljs.core (in this case `NONE`, added as private var to
|
||||
;; cljs.core with 1.9.562)
|
||||
|
|
@ -11,13 +12,14 @@
|
|||
#?(:clj [com.rpl.specter.util-macros :only [doseqres mk-comp-navs]]))
|
||||
|
||||
(:require [com.rpl.specter.protocols :as p]
|
||||
#?(:clj [clojure.pprint :as pp] :cljs [cljs.pprint :as pp])
|
||||
#?(:clj [clojure.pprint :as pp])
|
||||
[clojure.string :as s]
|
||||
[clojure.walk :as walk]
|
||||
#?(:clj [riddley.walk :as riddley]))
|
||||
|
||||
#?(:clj (:import [com.rpl.specter Util MutableCell])))
|
||||
#?(:bb [clojure.walk :as riddley]
|
||||
:clj [riddley.walk :as riddley]))
|
||||
|
||||
#?@(:bb []
|
||||
:clj [(:import [com.rpl.specter Util MutableCell])]))
|
||||
|
||||
(def NONE ::NONE)
|
||||
|
||||
|
|
@ -49,21 +51,6 @@
|
|||
([a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 & r] v)))
|
||||
|
||||
|
||||
#?(:clj
|
||||
(defmacro throw* [etype & args]
|
||||
`(throw (new ~etype (smart-str ~@args)))))
|
||||
|
||||
#?(
|
||||
:clj
|
||||
(defmacro throw-illegal [& args]
|
||||
`(throw* IllegalArgumentException ~@args))
|
||||
|
||||
|
||||
:cljs
|
||||
(defn throw-illegal [& args]
|
||||
(throw (js/Error. (apply str args)))))
|
||||
|
||||
|
||||
;; need to get the expansion function like this so that
|
||||
;; this code compiles in a clojure environment where cljs.analyzer
|
||||
;; namespace does not exist
|
||||
|
|
@ -85,7 +72,7 @@
|
|||
|
||||
:cljs
|
||||
(defn clj-macroexpand-all [form]
|
||||
(throw-illegal "not implemented")))
|
||||
(throw (ex-info "not implemented" {}))))
|
||||
|
||||
|
||||
#?(
|
||||
|
|
@ -94,9 +81,11 @@
|
|||
|
||||
:cljs
|
||||
(defn intern* [ns name val]
|
||||
(throw-illegal "intern not supported in ClojureScript")))
|
||||
(throw (ex-info "intern not supported in ClojureScript" {}))))
|
||||
|
||||
#?(
|
||||
#?(:bb
|
||||
(defmacro fast-object-array [i]
|
||||
`(object-array ~i))
|
||||
:clj
|
||||
(defmacro fast-object-array [i]
|
||||
`(com.rpl.specter.Util/makeObjectArray ~i)))
|
||||
|
|
@ -115,7 +104,8 @@
|
|||
(if (= platform :cljs)
|
||||
`(p/select* ~this ~@args)
|
||||
`(let [~hinted ~this]
|
||||
(.select* ~hinted ~@args)))))
|
||||
(#?(:bb p/select*
|
||||
:clj .select*) ~hinted ~@args)))))
|
||||
:cljs
|
||||
(defn exec-select* [this vals structure next-fn]
|
||||
(p/select* ^not-native this vals structure next-fn)))
|
||||
|
|
@ -129,7 +119,8 @@
|
|||
(if (= platform :cljs)
|
||||
`(p/transform* ~this ~@args)
|
||||
`(let [~hinted ~this]
|
||||
(.transform* ~hinted ~@args)))))
|
||||
(#?(:bb p/transform*
|
||||
:clj .transform*) ~hinted ~@args)))))
|
||||
|
||||
:cljs
|
||||
(defn exec-transform* [this vals structure next-fn]
|
||||
|
|
@ -148,7 +139,9 @@
|
|||
(defn- coerce-object [this]
|
||||
(cond (rich-nav? this) this
|
||||
(satisfies? p/ImplicitNav this) (p/implicit-nav this)
|
||||
:else (throw-illegal "Not a navigator: " this " " (pr-str (type this)))))
|
||||
:else (throw (ex-info "Not a navigator"
|
||||
{:this this
|
||||
:type-str (pr-str (type this))}))))
|
||||
|
||||
|
||||
(defprotocol CoercePath
|
||||
|
|
@ -177,7 +170,7 @@
|
|||
(coerce-path (vec this))))
|
||||
#?(:cljs cljs.core/Subvec)
|
||||
#?(:cljs (coerce-path [this]
|
||||
(coerce-path (vec this))))
|
||||
(coerce-path (into [] this))))
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(coerce-path [this]
|
||||
|
|
@ -226,13 +219,19 @@
|
|||
(set_cell [cell x])))
|
||||
|
||||
|
||||
#?(:cljs
|
||||
#?(:bb
|
||||
(defrecord MutableCell [x])
|
||||
:cljs
|
||||
(deftype MutableCell [^:volatile-mutable q]
|
||||
PMutableCell
|
||||
(set_cell [this x] (set! q x))))
|
||||
|
||||
|
||||
#?(
|
||||
#?(:bb
|
||||
(defn mutable-cell
|
||||
([] (mutable-cell nil))
|
||||
([v] (MutableCell. (volatile! v))))
|
||||
|
||||
:clj
|
||||
(defn mutable-cell
|
||||
([] (mutable-cell nil))
|
||||
|
|
@ -244,7 +243,10 @@
|
|||
([init] (MutableCell. init))))
|
||||
|
||||
|
||||
#?(
|
||||
#?(:bb
|
||||
(defn set-cell! [^MutableCell c v]
|
||||
(vreset! (:x c) v))
|
||||
|
||||
:clj
|
||||
(defn set-cell! [^MutableCell c v]
|
||||
(.set c v))
|
||||
|
|
@ -254,7 +256,10 @@
|
|||
(set_cell cell val)))
|
||||
|
||||
|
||||
#?(
|
||||
#?(:bb
|
||||
(defn get-cell [^MutableCell c]
|
||||
@(:x c))
|
||||
|
||||
:clj
|
||||
(defn get-cell [^MutableCell c]
|
||||
(.get c))
|
||||
|
|
@ -302,8 +307,10 @@
|
|||
(defn do-compiled-traverse* [apath structure]
|
||||
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
|
||||
(#?(:clj reduce :cljs -reduce)
|
||||
[this afn]
|
||||
(#?(:clj .reduce :cljs -reduce) this afn (afn)))
|
||||
[this afn]
|
||||
#?(:bb (reduce afn (afn) this)
|
||||
:default
|
||||
(#?(:clj .reduce :cljs -reduce) this afn (afn))))
|
||||
(#?(:clj reduce :cljs -reduce)
|
||||
[this afn start]
|
||||
(let [cell (mutable-cell start)]
|
||||
|
|
@ -319,15 +326,31 @@
|
|||
(get-cell cell)
|
||||
))))
|
||||
|
||||
#?(
|
||||
:bb
|
||||
(defn- call-reduce-interface [^clojure.lang.IReduce traverser afn start]
|
||||
(reduce afn start traverser))
|
||||
:clj
|
||||
(defn- call-reduce-interface [^clojure.lang.IReduce traverser afn start]
|
||||
(.reduce traverser afn start)
|
||||
)
|
||||
|
||||
:cljs
|
||||
(defn- call-reduce-interface [^cljs.core/IReduce traverser afn start]
|
||||
(-reduce traverser afn start)
|
||||
))
|
||||
|
||||
(defn do-compiled-traverse [apath structure]
|
||||
(let [traverser (do-compiled-traverse* apath structure)]
|
||||
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
|
||||
(#?(:clj reduce :cljs -reduce)
|
||||
[this afn]
|
||||
(#?(:clj .reduce :cljs -reduce) this afn (afn)))
|
||||
[this afn]
|
||||
#?(:bb (reduce afn (afn) this)
|
||||
:default
|
||||
(#?(:clj .reduce :cljs -reduce) this afn (afn))))
|
||||
(#?(:clj reduce :cljs -reduce)
|
||||
[this afn start]
|
||||
(let [res (#?(:clj .reduce :cljs -reduce) traverser afn start)]
|
||||
(let [res (call-reduce-interface traverser afn start)]
|
||||
(unreduced res)
|
||||
)))))
|
||||
|
||||
|
|
@ -361,7 +384,8 @@
|
|||
(let [curr (get-cell res)]
|
||||
(if (identical? curr NONE)
|
||||
(set-cell! res structure)
|
||||
(throw-illegal "More than one element found in structure: " structure))))]
|
||||
(throw (ex-info "More than one element found in structure"
|
||||
{:structure structure})))))]
|
||||
|
||||
(compiled-traverse* path result-fn structure)
|
||||
(let [ret (get-cell res)]
|
||||
|
|
@ -376,11 +400,12 @@
|
|||
(let [curr (get-cell res)]
|
||||
(if (identical? curr NONE)
|
||||
(set-cell! res structure)
|
||||
(throw-illegal "More than one element found in structure: " structure))))]
|
||||
(throw (ex-info "More than one element found in structure"
|
||||
{:structure structure})))))]
|
||||
(compiled-traverse* path result-fn structure)
|
||||
(let [ret (get-cell res)]
|
||||
(if (identical? NONE ret)
|
||||
(throw-illegal "Found no elements for select-one! on " structure))
|
||||
(throw (ex-info "Found no elements for select-one!" {:structure structure})))
|
||||
ret)))
|
||||
|
||||
|
||||
|
|
@ -412,6 +437,9 @@
|
|||
(fn [vals structure]
|
||||
(terminal* transform-fn vals structure))))
|
||||
|
||||
(defn compiled-vtransform* [nav transform-fn structure]
|
||||
(exec-transform* nav [] structure transform-fn))
|
||||
|
||||
(defn fn-invocation? [f]
|
||||
(or #?(:clj (instance? clojure.lang.Cons f))
|
||||
#?(:clj (instance? clojure.lang.LazySeq f))
|
||||
|
|
@ -421,8 +449,10 @@
|
|||
(defrecord LocalSym
|
||||
[val sym])
|
||||
|
||||
;; needs to be named "avar" instead of "var" due to regression in cljs circa
|
||||
;; 6/26/2017. See https://github.com/nathanmarz/specter/issues/215
|
||||
(defrecord VarUse
|
||||
[val var sym])
|
||||
[val avar sym])
|
||||
|
||||
(defrecord SpecialFormUse
|
||||
[val code])
|
||||
|
|
@ -686,16 +716,18 @@
|
|||
(preserve-map magic-precompilation* o)
|
||||
|
||||
(instance? VarUse o)
|
||||
(if (dynamic-var? (:var o))
|
||||
(->DynamicVal (maybe-direct-nav
|
||||
(:sym o)
|
||||
(or (-> o :var direct-nav?)
|
||||
(-> o :sym direct-nav?))))
|
||||
(maybe-direct-nav
|
||||
(:val o)
|
||||
(or (-> o :var direct-nav?)
|
||||
(-> o :sym direct-nav?)
|
||||
(-> o :val direct-nav?))))
|
||||
(let [v (:avar o)]
|
||||
;; v can be nil if the symbol referred to an imported class
|
||||
(if (and v (dynamic-var? v))
|
||||
(->DynamicVal (maybe-direct-nav
|
||||
(:sym o)
|
||||
(or (direct-nav? v)
|
||||
(-> o :sym direct-nav?))))
|
||||
(maybe-direct-nav
|
||||
(:val o)
|
||||
(or (and v (direct-nav? v))
|
||||
(-> o :sym direct-nav?)
|
||||
(-> o :val direct-nav?)))))
|
||||
|
||||
(instance? LocalSym o)
|
||||
(->DynamicVal (:sym o))
|
||||
|
|
@ -791,7 +823,8 @@
|
|||
(defn dynamic-val-code [code possible-params]
|
||||
(let [[i] (keep-indexed (fn [i v] (if (= v code) i)) possible-params)]
|
||||
(if (nil? i)
|
||||
(throw-illegal "Could not find " code " in possible params " possible-params))
|
||||
(throw (ex-info "Could not find code in possible params"
|
||||
{:code code :possible-params possible-params})))
|
||||
(maybe-direct-nav
|
||||
(->LocalParam i)
|
||||
(direct-nav? code)))))
|
||||
|
|
@ -886,6 +919,8 @@
|
|||
(if (fn? e) (re-find #" .*" (pr-str e)) e))
|
||||
o)))
|
||||
|
||||
(def ^:dynamic *path-compile-files* false)
|
||||
|
||||
#?(:clj
|
||||
(defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-param]
|
||||
(let [code `(fn [~@used-locals-list] ~resolved-code)
|
||||
|
|
@ -894,7 +929,11 @@
|
|||
(println "Produced code:")
|
||||
(pp/pprint code)
|
||||
(println))
|
||||
(binding [*ns* ns] (eval+ code))))
|
||||
(binding [*ns* ns
|
||||
*compile-files* (if *path-compile-files*
|
||||
*compile-files*
|
||||
false)]
|
||||
(eval+ code))))
|
||||
|
||||
:cljs
|
||||
(defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-params]
|
||||
|
|
@ -902,7 +941,7 @@
|
|||
(println "Possible params:")
|
||||
(println possible-params)
|
||||
(println "\nProduced dynamic object:")
|
||||
(pp/pprint (mk-fn-name-strs resolved-code))
|
||||
(println (mk-fn-name-strs resolved-code))
|
||||
(println))
|
||||
(fn [dynamic-params]
|
||||
(late-resolve resolved-code dynamic-params))))
|
||||
|
|
@ -957,9 +996,9 @@
|
|||
|
||||
|
||||
(defn- multi-transform-error-fn [& nav]
|
||||
(throw-illegal
|
||||
"All navigation in multi-transform must end in 'terminal' "
|
||||
"navigators. Instead navigated to: " nav))
|
||||
(throw
|
||||
(ex-info "All navigation in multi-transform must end in 'terminal' navigators"
|
||||
{:nav nav})))
|
||||
|
||||
(defn compiled-multi-transform* [path structure]
|
||||
(compiled-transform* path multi-transform-error-fn structure))
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
(defn- determine-params-impls [impls]
|
||||
(let [grouped (->> impls (map (fn [[n & body]] [n body])) (into {}))]
|
||||
(if-not (= #{'select* 'transform*} (-> grouped keys set))
|
||||
(i/throw-illegal "defnav must implement select* and transform*, instead got "
|
||||
(keys grouped)))
|
||||
(throw (ex-info "defnav must implement select* and transform*"
|
||||
{:methods (keys grouped)})))
|
||||
grouped))
|
||||
|
||||
|
||||
|
|
@ -39,11 +39,16 @@
|
|||
(let [helpers (for [[mname [_ & mparams] & mbody] impls]
|
||||
`(defn ~(helper-name name mname) [~@params ~@mparams] ~@mbody))
|
||||
decls (for [[mname & _] impls]
|
||||
`(declare ~(helper-name name mname)))]
|
||||
`(declare ~(helper-name name mname)))
|
||||
name-with-meta (vary-meta name
|
||||
assoc :arglists (list 'quote (list params)))]
|
||||
`(do
|
||||
~@decls
|
||||
~@helpers
|
||||
(def ~name (nav ~params ~@impls)))))
|
||||
(def ~name-with-meta (nav ~params ~@impls)))))
|
||||
|
||||
(defmacro defrichnav [name params & impls]
|
||||
`(def ~name (richnav ~params ~@impls)))
|
||||
(let [name-with-meta (vary-meta name
|
||||
assoc :arglists (list 'quote (list params)))]
|
||||
`(def ~name-with-meta
|
||||
(richnav ~params ~@impls))))
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@
|
|||
[defnav defrichnav]]
|
||||
[com.rpl.specter.util-macros :refer
|
||||
[doseqres]]))
|
||||
(:use #?(:clj [com.rpl.specter.macros :only [defnav defrichnav]])
|
||||
#?(:clj [com.rpl.specter.util-macros :only [doseqres]]))
|
||||
#?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]]
|
||||
[com.rpl.specter.util-macros :only [doseqres]]))
|
||||
(:require [com.rpl.specter.impl :as i]
|
||||
#?(:clj [clojure.core.reducers :as r])))
|
||||
#?@(:bb []
|
||||
:clj [[clojure.core.reducers :as r]])))
|
||||
|
||||
|
||||
(defn not-selected?*
|
||||
|
|
@ -73,7 +74,6 @@
|
|||
nil)
|
||||
|
||||
|
||||
;; in cljs they're PersistentVector so don't need a special case
|
||||
#?(:clj clojure.lang.MapEntry)
|
||||
#?(:clj
|
||||
(all-transform [structure next-fn]
|
||||
|
|
@ -82,12 +82,26 @@
|
|||
(clojure.lang.MapEntry. newk newv))))
|
||||
|
||||
|
||||
#?(:cljs cljs.core/MapEntry)
|
||||
#?(:cljs
|
||||
(all-transform [structure next-fn]
|
||||
(let [newk (next-fn (key structure))
|
||||
newv (next-fn (val structure))]
|
||||
(cljs.core/->MapEntry newk newv nil))))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
(all-transform [structure next-fn]
|
||||
(into []
|
||||
(comp (map next-fn)
|
||||
(filter not-NONE?))
|
||||
structure))
|
||||
|
||||
#?(:clj String :cljs string)
|
||||
(all-transform [structure next-fn]
|
||||
(apply str (into []
|
||||
(comp (map next-fn)
|
||||
(filter not-NONE?))
|
||||
structure)))
|
||||
|
||||
#?(:clj clojure.lang.PersistentHashSet :cljs cljs.core/PersistentHashSet)
|
||||
(all-transform [structure next-fn]
|
||||
|
|
@ -97,7 +111,10 @@
|
|||
structure))
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap)
|
||||
#?(:clj
|
||||
#?(:bb
|
||||
(all-transform [structure next-fn]
|
||||
(non-transient-map-all-transform structure next-fn {}))
|
||||
:clj
|
||||
(all-transform [structure next-fn]
|
||||
(let [k-it (.keyIterator structure)
|
||||
v-it (.valIterator structure)
|
||||
|
|
@ -179,10 +196,13 @@
|
|||
|
||||
|
||||
:else
|
||||
(->> structure
|
||||
(r/map next-fn)
|
||||
(r/filter not-NONE?)
|
||||
(into empty-structure))))))
|
||||
#?(:bb (into empty-structure
|
||||
(comp (map next-fn) (filter not-NONE?))
|
||||
structure)
|
||||
:clj (->> structure
|
||||
(r/map next-fn)
|
||||
(r/filter not-NONE?)
|
||||
(into empty-structure)))))))
|
||||
|
||||
|
||||
#?(:cljs default)
|
||||
|
|
@ -249,7 +269,10 @@
|
|||
|
||||
|
||||
#?(:clj clojure.lang.PersistentArrayMap)
|
||||
#?(:clj
|
||||
#?(:bb
|
||||
(map-vals-transform [structure next-fn]
|
||||
(map-vals-non-transient-transform structure {} next-fn))
|
||||
:clj
|
||||
(map-vals-transform [structure next-fn]
|
||||
(let [k-it (.keyIterator structure)
|
||||
v-it (.valIterator structure)
|
||||
|
|
@ -276,7 +299,10 @@
|
|||
array
|
||||
)]
|
||||
(clojure.lang.PersistentArrayMap. array)))))
|
||||
#?(:clj
|
||||
#?(:bb
|
||||
(map-keys-transform [structure next-fn]
|
||||
(map-keys-non-transient-transform structure {} next-fn))
|
||||
:clj
|
||||
(map-keys-transform [structure next-fn]
|
||||
(let [k-it (.keyIterator structure)
|
||||
v-it (.valIterator structure)
|
||||
|
|
@ -443,6 +469,24 @@
|
|||
(prepend-one [structure elem]
|
||||
(into [elem] structure))
|
||||
|
||||
#?(:cljs cljs.core/Subvec)
|
||||
#?(:cljs
|
||||
(append-all [structure elements]
|
||||
(reduce conj structure elements)))
|
||||
#?(:cljs
|
||||
(prepend-all [structure elements]
|
||||
(let [ret (transient [])]
|
||||
(as-> ret <>
|
||||
(reduce conj! <> elements)
|
||||
(reduce conj! <> structure)
|
||||
(persistent! <>)))))
|
||||
#?(:cljs
|
||||
(append-one [structure elem]
|
||||
(conj structure elem)))
|
||||
#?(:cljs
|
||||
(prepend-one [structure elem]
|
||||
(into [elem] structure)))
|
||||
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(append-all [structure elements]
|
||||
|
|
@ -468,6 +512,9 @@
|
|||
(defprotocol FastEmpty
|
||||
(fast-empty? [s]))
|
||||
|
||||
(defprotocol InsertBeforeIndex
|
||||
(insert-before-idx [aseq idx val]))
|
||||
|
||||
(defnav PosNavigator [getter updater]
|
||||
(select* [this structure next-fn]
|
||||
(if-not (fast-empty? structure)
|
||||
|
|
@ -478,6 +525,18 @@
|
|||
structure
|
||||
(updater structure next-fn))))
|
||||
|
||||
#?(:bb
|
||||
(defn vec-count [v]
|
||||
(count v))
|
||||
|
||||
:clj
|
||||
(defn vec-count [^clojure.lang.IPersistentVector v]
|
||||
(.length v))
|
||||
|
||||
:cljs
|
||||
(defn vec-count [v]
|
||||
(count v)))
|
||||
|
||||
(defn- update-first-list [l afn]
|
||||
(let [newf (afn (first l))
|
||||
restl (rest l)]
|
||||
|
|
@ -492,17 +551,39 @@
|
|||
(if (nil? bl) '() bl)
|
||||
(concat bl [lastl]))))
|
||||
|
||||
#?(
|
||||
:clj
|
||||
(defn vec-count [^clojure.lang.IPersistentVector v]
|
||||
(.length v))
|
||||
(defn- update-first-vector [v afn]
|
||||
(let [val (nth v 0)
|
||||
newv (afn val)]
|
||||
(if (identical? i/NONE newv)
|
||||
(subvec v 1)
|
||||
(assoc v 0 newv)
|
||||
)))
|
||||
|
||||
:cljs
|
||||
(defn vec-count [v]
|
||||
(count v)))
|
||||
(defn- update-last-vector [v afn]
|
||||
;; type-hinting vec-count to ^int caused weird errors with case
|
||||
(let [c (int (vec-count v))]
|
||||
(case c
|
||||
1 (let [[e] v
|
||||
newe (afn e)]
|
||||
(if (identical? i/NONE newe)
|
||||
[]
|
||||
[newe]))
|
||||
2 (let [[e1 e2] v
|
||||
newe (afn e2)]
|
||||
(if (identical? i/NONE newe)
|
||||
[e1]
|
||||
[e1 newe]))
|
||||
(let [i (dec c)
|
||||
newe (afn (nth v i))]
|
||||
(if (identical? i/NONE newe)
|
||||
(pop v)
|
||||
(assoc v i newe))))))
|
||||
|
||||
|
||||
#?(
|
||||
#?(:bb
|
||||
(defn transient-vec-count [v]
|
||||
(count v))
|
||||
|
||||
:clj
|
||||
(defn transient-vec-count [^clojure.lang.ITransientVector v]
|
||||
(.count v))
|
||||
|
|
@ -515,32 +596,18 @@
|
|||
(extend-protocol UpdateExtremes
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
(update-first [v afn]
|
||||
(let [val (nth v 0)
|
||||
newv (afn val)]
|
||||
(if (identical? i/NONE newv)
|
||||
(subvec v 1)
|
||||
(assoc v 0 newv)
|
||||
)))
|
||||
(update-first-vector v afn))
|
||||
|
||||
(update-last [v afn]
|
||||
;; type-hinting vec-count to ^int caused weird errors with case
|
||||
(let [c (int (vec-count v))]
|
||||
(case c
|
||||
1 (let [[e] v
|
||||
newe (afn e)]
|
||||
(if (identical? i/NONE newe)
|
||||
[]
|
||||
[newe]))
|
||||
2 (let [[e1 e2] v
|
||||
newe (afn e2)]
|
||||
(if (identical? i/NONE newe)
|
||||
[e1]
|
||||
[e1 newe]))
|
||||
(let [i (dec c)
|
||||
newe (afn (nth v i))]
|
||||
(if (identical? i/NONE newe)
|
||||
(pop v)
|
||||
(assoc v i newe))))))
|
||||
(update-last-vector v afn))
|
||||
|
||||
#?(:cljs cljs.core/Subvec)
|
||||
#?(:cljs
|
||||
(update-first [v afn]
|
||||
(update-first-vector v afn)))
|
||||
#?(:cljs
|
||||
(update-last [v afn]
|
||||
(update-last-vector v afn)))
|
||||
|
||||
#?(:clj String :cljs string)
|
||||
(update-first [s afn]
|
||||
|
|
@ -559,6 +626,14 @@
|
|||
(str begins newl)
|
||||
)))
|
||||
|
||||
#?(:cljs cljs.core/MapEntry)
|
||||
#?(:cljs
|
||||
(update-first [e afn]
|
||||
(cljs.core/->MapEntry (-> e key afn) (val e) nil)))
|
||||
#?(:cljs
|
||||
(update-last [e afn]
|
||||
(cljs.core/->MapEntry (key e) (-> e val afn) nil)))
|
||||
|
||||
#?(:clj Object :cljs default)
|
||||
(update-first [l val]
|
||||
(update-first-list l val))
|
||||
|
|
@ -579,6 +654,14 @@
|
|||
(get-last [s]
|
||||
(last s))
|
||||
|
||||
#?(:cljs cljs.core/MapEntry)
|
||||
#?(:cljs
|
||||
(get-first [e]
|
||||
(key e)))
|
||||
#?(:cljs
|
||||
(get-last [e]
|
||||
(val e)))
|
||||
|
||||
#?(:clj String :cljs string)
|
||||
(get-first [s]
|
||||
(nth s 0))
|
||||
|
|
@ -668,3 +751,36 @@
|
|||
((:end-fn end-fn) structure start)
|
||||
(end-fn structure)
|
||||
))
|
||||
|
||||
(defn- insert-before-index-list [lst idx val]
|
||||
;; an implementation that is most efficient for list style structures
|
||||
(let [[front back] (split-at idx lst)]
|
||||
(concat front (cons val back))))
|
||||
|
||||
(extend-protocol InsertBeforeIndex
|
||||
nil
|
||||
(insert-before-idx [_ idx val]
|
||||
(if (= 0 idx)
|
||||
(list val)
|
||||
(throw (ex-info "For a nil structure, can only insert before index 0"
|
||||
{:insertion-index idx}))))
|
||||
|
||||
#?(:clj java.lang.String :cljs string)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(apply str (insert-before-index-list aseq idx val)))
|
||||
|
||||
#?(:clj clojure.lang.LazySeq :cljs cljs.core/LazySeq)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(insert-before-index-list aseq idx val))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(let [front (subvec aseq 0 idx)
|
||||
back (subvec aseq idx)]
|
||||
(into (conj front val) back)))
|
||||
|
||||
#?(:clj clojure.lang.IPersistentList :cljs cljs.core/List)
|
||||
(insert-before-idx [aseq idx val]
|
||||
(cond (= idx 0)
|
||||
(cons val aseq)
|
||||
:else (insert-before-index-list aseq idx val))))
|
||||
|
|
|
|||
|
|
@ -65,4 +65,4 @@
|
|||
`(defn ~'late-fn [~f ~args]
|
||||
(case (count ~args)
|
||||
~@(apply concat cases)
|
||||
(com.rpl.specter.impl/throw-illegal "Cannot have late function with more than 18 args")))))
|
||||
(throw (ex-info "Cannot have late function with more than 18 args" {}))))))
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
select-first transform setval replace-in
|
||||
select-any selected-any? collected? traverse
|
||||
multi-transform path dynamicnav recursive-path
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn]]))
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn
|
||||
vtransform]]))
|
||||
(:use
|
||||
#?(:clj [clojure.test :only [deftest is]])
|
||||
#?(:clj [clojure.test.check.clojure-test :only [defspec]])
|
||||
|
|
@ -21,7 +22,8 @@
|
|||
select-first transform setval replace-in
|
||||
select-any selected-any? collected? traverse
|
||||
multi-transform path dynamicnav recursive-path
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn]]))
|
||||
defdynamicnav traverse-all satisfies-protpath? end-fn
|
||||
vtransform]]))
|
||||
|
||||
|
||||
|
||||
|
|
@ -1318,9 +1320,13 @@
|
|||
(deftest traversed-test
|
||||
(is (= 10 (select-any (s/traversed s/ALL +) [1 2 3 4]))))
|
||||
|
||||
(defn- predand= [pred ret v]
|
||||
(and (pred ret)
|
||||
(= ret v)))
|
||||
(defn- predand= [pred v1 v2]
|
||||
(and (pred v1)
|
||||
(pred v2)
|
||||
(= v1 v2)))
|
||||
|
||||
(defn listlike? [v]
|
||||
(or (list? v) (seq? v)))
|
||||
|
||||
(deftest nthpath-test
|
||||
(is (predand= vector? [1 2 -3 4] (transform (s/nthpath 2) - [1 2 3 4])))
|
||||
|
|
@ -1332,7 +1338,7 @@
|
|||
|
||||
(deftest remove-with-NONE-test
|
||||
(is (predand= vector? [1 2 3] (setval [s/ALL nil?] s/NONE [1 2 nil 3 nil])))
|
||||
(is (predand= list? '(1 2 3) (setval [s/ALL nil?] s/NONE '(1 2 nil 3 nil))))
|
||||
(is (predand= listlike? '(1 2 3) (setval [s/ALL nil?] s/NONE '(1 2 nil 3 nil))))
|
||||
(is (= {:b 2} (setval :a s/NONE {:a 1 :b 2})))
|
||||
(is (= {:b 2} (setval (s/must :a) s/NONE {:a 1 :b 2})))
|
||||
(is (predand= vector? [1 3] (setval (s/keypath 1) s/NONE [1 2 3])))
|
||||
|
|
@ -1434,13 +1440,38 @@
|
|||
(is (= "abq" (setval s/LAST "q" "abc")))
|
||||
)
|
||||
|
||||
(defn whitespace? [char]
|
||||
(re-matches #"\s" (str char)))
|
||||
|
||||
(deftest string-transform-test
|
||||
(is (= "123" (transform s/ALL identity "123")))
|
||||
(is (= "123" (transform [s/ALL whitespace?] s/NONE "1 2 3")))
|
||||
#?(:clj (is (= "123" (setval [s/ALL #(Character/isWhitespace %)] s/NONE "1 2 3"))))
|
||||
#?(:clj (is (= "123" (transform [(s/filterer #(Character/isWhitespace %))] s/NONE "1 2 3")))))
|
||||
|
||||
(deftest regex-navigation-test
|
||||
;; also test regexes as implicit navs
|
||||
(is (= (select #"t" "test") ["t" "t"]))
|
||||
(is (= (select [:a (s/regex-nav #"t")] {:a "test"}) ["t" "t"]))
|
||||
(is (= (transform (s/regex-nav #"t") clojure.string/capitalize "test") "TesT"))
|
||||
;; also test regexes as implicit navs
|
||||
(is (= (transform [:a #"t"] clojure.string/capitalize {:a "test"}) {:a "TesT"}))
|
||||
(is (= (transform (s/regex-nav #"\s+\w") clojure.string/triml "Hello World!") "HelloWorld!"))
|
||||
(is (= (setval (s/regex-nav #"t") "z" "test") "zesz"))
|
||||
(is (= (setval [:a (s/regex-nav #"t")] "z" {:a "test"}) {:a "zesz"}))
|
||||
(is (= (transform (s/regex-nav #"aa*") (fn [s] (-> s count str)) "aadt") "2dt"))
|
||||
(is (= (transform (s/regex-nav #"[Aa]+") (fn [s] (apply str (take (count s) (repeat "@")))) "Amsterdam Aardvarks") "@msterd@m @@rdv@rks"))
|
||||
(is (= (select [(s/regex-nav #"(\S+):\ (\d+)") (s/nthpath 2)] "Mary: 1st George: 2nd Arthur: 3rd") ["1" "2" "3"]))
|
||||
(is (= (transform (s/subselect (s/regex-nav #"\d\w+")) reverse "Mary: 1st George: 2nd Arthur: 3rd") "Mary: 3rd George: 2nd Arthur: 1st"))
|
||||
)
|
||||
|
||||
(deftest single-value-none-navigators-test
|
||||
(is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2])))
|
||||
(is (predand= list? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
|
||||
(is (predand= list? '(1) (setval s/AFTER-ELEM 1 nil)))
|
||||
(is (predand= listlike? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
|
||||
(is (predand= listlike? '(1) (setval s/AFTER-ELEM 1 nil)))
|
||||
(is (predand= vector? [3 1 2] (setval s/BEFORE-ELEM 3 [1 2])))
|
||||
(is (predand= list? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
|
||||
(is (predand= list? '(1) (setval s/BEFORE-ELEM 1 nil)))
|
||||
(is (predand= listlike? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
|
||||
(is (predand= listlike? '(1) (setval s/BEFORE-ELEM 1 nil)))
|
||||
(is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2})))
|
||||
(is (= #{1} (setval s/NONE-ELEM 1 nil)))
|
||||
)
|
||||
|
|
@ -1459,10 +1490,10 @@
|
|||
|
||||
(defspec map-keys-all-first-equivalence-transform
|
||||
(for-all+
|
||||
[m (limit-size 10 (gen/map gen/int gen/keyword))]
|
||||
(= (transform s/MAP-KEYS inc m)
|
||||
(transform [s/ALL s/FIRST] inc m )
|
||||
)))
|
||||
[m (limit-size 10 (gen/map gen/int gen/keyword))]
|
||||
(= (transform s/MAP-KEYS inc m)
|
||||
(transform [s/ALL s/FIRST] inc m )
|
||||
)))
|
||||
|
||||
(defspec map-keys-all-first-equivalence-select
|
||||
(for-all+
|
||||
|
|
@ -1484,7 +1515,7 @@
|
|||
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
|
||||
(let [newl (setval s/FIRST s/NONE l)]
|
||||
(and (= newl (rest l))
|
||||
(list? newl)
|
||||
(listlike? newl)
|
||||
))))
|
||||
|
||||
(defspec remove-last-vector
|
||||
|
|
@ -1517,6 +1548,8 @@
|
|||
|
||||
(deftest filterer-remove-test
|
||||
(is (= [1 :a 3 5] (setval (s/filterer even?) [:a] [1 2 3 4 5])))
|
||||
(is (= [1 3 5] (setval (s/filterer even?) [] [1 2 3 4 5])))
|
||||
(is (= [1 3 5] (setval (s/filterer even?) nil [1 2 3 4 5])))
|
||||
)
|
||||
|
||||
(deftest helper-preds-test
|
||||
|
|
@ -1577,6 +1610,98 @@
|
|||
(is (= [1 3 3] (transform MIDDLE inc [1 2 3])))
|
||||
)
|
||||
|
||||
(def ^:dynamic *dvar* :a)
|
||||
|
||||
(defn dvar-tester []
|
||||
(select-any *dvar* {:a 1 :b 2}))
|
||||
|
||||
(deftest dynamic-var-ic-test
|
||||
(is (= 1 (dvar-tester)))
|
||||
(is (= 2 (binding [*dvar* :b] (dvar-tester))))
|
||||
)
|
||||
|
||||
(deftest before-index-test
|
||||
(let [data [1 2 3]
|
||||
datal '(1 2 3)
|
||||
data-str "abcdef"]
|
||||
(is (predand= vector? [:a 1 2 3] (setval (s/before-index 0) :a data)))
|
||||
(is (predand= vector? [1 2 3] (setval (s/before-index 1) s/NONE data)))
|
||||
(is (predand= vector? [1 :a 2 3] (setval (s/before-index 1) :a data)))
|
||||
(is (predand= vector? [1 2 3 :a] (setval (s/before-index 3) :a data)))
|
||||
; ensure inserting at index 0 in nil structure works, as in previous impl
|
||||
(is (predand= listlike? '(:a) (setval (s/before-index 0) :a nil)))
|
||||
(is (predand= listlike? '(:a 1 2 3) (setval (s/before-index 0) :a datal)))
|
||||
(is (predand= listlike? '(1 :a 2 3) (setval (s/before-index 1) :a datal)))
|
||||
(is (predand= listlike? '(1 2 3 :a) (setval (s/before-index 3) :a datal)))
|
||||
(is (predand= string? "abcxdef" (setval (s/before-index 3) (char \x) data-str)))
|
||||
))
|
||||
|
||||
(deftest index-nav-test
|
||||
(let [data [1 2 3 4 5 6]
|
||||
datal '(1 2 3 4 5 6)]
|
||||
(is (predand= vector? [3 1 2 4 5 6] (setval (s/index-nav 2) 0 data)))
|
||||
(is (predand= vector? [1 3 2 4 5 6] (setval (s/index-nav 2) 1 data)))
|
||||
(is (predand= vector? [1 2 3 4 5 6] (setval (s/index-nav 2) 2 data)))
|
||||
(is (predand= vector? [1 2 4 5 3 6] (setval (s/index-nav 2) 4 data)))
|
||||
(is (predand= vector? [1 2 4 5 6 3] (setval (s/index-nav 2) 5 data)))
|
||||
(is (predand= vector? [6 1 2 3 4 5] (setval (s/index-nav 5) 0 data)))
|
||||
|
||||
(is (predand= listlike? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal)))
|
||||
(is (predand= listlike? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal)))
|
||||
(is (predand= listlike? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal)))
|
||||
(is (predand= listlike? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal)))
|
||||
(is (predand= listlike? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal)))
|
||||
(is (predand= listlike? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal)))
|
||||
))
|
||||
|
||||
(deftest indexed-vals-test
|
||||
(let [data [:a :b :c :d :e]]
|
||||
(is (= [[0 :a] [1 :b] [2 :c] [3 :d] [4 :e]] (select s/INDEXED-VALS data)))
|
||||
(is (= [:e :d :c :b :a] (setval [s/INDEXED-VALS s/FIRST] 0 data)))
|
||||
(is (= [:a :b :e :d :c] (setval [s/INDEXED-VALS s/FIRST] 2 data)))
|
||||
(is (= [:b :a :d :c :e] (transform [s/INDEXED-VALS s/FIRST odd?] dec data)))
|
||||
(is (= [:a :b :c :d :e] (transform [s/INDEXED-VALS s/FIRST odd?] inc data)))
|
||||
(is (= [0 2 2 4] (transform [s/INDEXED-VALS s/LAST odd?] inc [0 1 2 3])))
|
||||
(is (= [0 1 2 3] (transform [s/INDEXED-VALS (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [2 1 3 0])))
|
||||
(is (= [-1 0 1 2 3] (transform [(s/indexed-vals -1) (s/collect-one s/LAST) s/FIRST] (fn [i _] i) [3 -1 0 2 1])))
|
||||
(is (= [[1 :a] [2 :b] [3 :c]] (select (s/indexed-vals 1) [:a :b :c])))
|
||||
))
|
||||
|
||||
(deftest other-implicit-navs-test
|
||||
(is (= 1 (select-any ["a" true \c 10 'd] {"a" {true {\c {10 {'d 1}}}}})))
|
||||
)
|
||||
|
||||
(deftest vterminal-test
|
||||
(is (= {:a {:b [[1 2] 3]}}
|
||||
(multi-transform [(s/putval 1) :a (s/putval 2) :b (s/vterminal (fn [vs v] [vs v]))]
|
||||
{:a {:b 3}})))
|
||||
)
|
||||
|
||||
(deftest vtransform-test
|
||||
(is (= {:a 6} (vtransform [:a (s/putval 2) (s/putval 3)] (fn [vs v] (+ v (reduce + vs))) {:a 1})))
|
||||
)
|
||||
|
||||
(deftest compact-test
|
||||
(is (= {} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1}}})))
|
||||
(is (= {:a {:d 2}} (setval [:a (s/compact :b :c)] s/NONE {:a {:b {:c 1} :d 2}})))
|
||||
(let [TREE-VALUES (recursive-path [] p (s/if-path vector? [(s/compact s/ALL) p] s/STAY))
|
||||
tree [1 [2 3] [] [4 [[5] [[6]]]]]]
|
||||
(is (= [2 4 6] (select [TREE-VALUES even?] tree)))
|
||||
(is (= [1 [3] [[[5]]]] (setval [TREE-VALUES even?] s/NONE tree)))
|
||||
)
|
||||
(is (= [{:a [{:c 1}]}]
|
||||
(setval [s/ALL (s/compact :a s/ALL :b)]
|
||||
s/NONE
|
||||
[{:a [{:b 3}]}
|
||||
{:a [{:b 2 :c 1}]}])))
|
||||
)
|
||||
|
||||
(deftest class-constant-test
|
||||
(let [f (fn [p] (fn [v] (str p (inc v))))]
|
||||
(is (= (str #?(:clj String :cljs js/String) 2)
|
||||
(multi-transform (s/terminal (f #?(:clj String :cljs js/String))) 1)))
|
||||
))
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(defprotocolpath FooPP)
|
||||
|
|
|
|||
Loading…
Reference in a new issue