Compare commits

...

42 commits

Author SHA1 Message Date
tommy
9a615ff22c
Implement ALL for strings (#333) 2024-09-16 05:10:52 -10:00
Nathan Marz
67e8680602 update changelog 2022-03-18 07:45:26 -10:00
Nathan Marz
05ae730896 1.1.5-SNAPSHOT 2022-03-18 07:42:58 -10:00
Nathan Marz
a91aaaa34a 1.1.4 2022-03-18 07:41:05 -10:00
Michiel Borkent
efeefdfacd
Update CHANGES.md (#324) 2022-03-14 09:46:29 -10:00
Michiel Borkent
a64209d582
Make babashka-compatible (#323) 2022-03-14 07:11:40 -10:00
Joshua Suskalo
d0d6fdf581
Add section to the readme with clj-kondo configuration snippet. (#310) (#311) 2021-06-02 13:47:44 -10:00
Jeff Evans
e8225f0e58
Make it possible to run individual benchmark(s) (#298)
Parse command line args to benchmarks.clj and treat them as benchmark names to run

Add information on running benchmarks to DEVELOPER.md
2020-10-22 17:51:04 -04:00
Nathan Marz
a379893598 BEFORE-ELEM, AFTER-ELEM, FIRST, LAST, BEGINNING, and END on subvecs now produce vector type in cljs 2020-09-17 15:47:26 -04:00
Nathan Marz
efaf35558a replace throw-illegal with ex-info 2020-09-17 15:36:16 -04:00
Nathan Marz
083376f904 update changelog 2020-09-17 15:26:28 -04:00
Nathan Marz
de48f24d70 update changelog 2020-09-17 15:25:22 -04:00
Nathan Marz
c54a46c686 before-index 0 on nil produces list, fix flaws in tests regarding predand= 2020-09-17 15:24:11 -04:00
Jeff Evans
2e002c1270
Improve before-index performance #223 (#291)
Adding new protocol for performing the insert-before-idx operation, with
implementations for core collection types

Adding new functional test to confirm behavior when operating on a string

Adding benchmarks to compare new performance vs old implementation vs
core Clojure in a couple of cases
2020-09-17 15:15:42 -04:00
Jeff Evans
789881b3ad
Fix instructions for running tests (#295) 2020-09-14 15:37:16 -04:00
Nathan Marz
edd7429281 update changelog 2020-08-16 19:40:59 -04:00
Adrian
e222ba2a6c
Add arglist metadata to navs. (#290) 2020-08-16 19:40:02 -04:00
Nathan Marz
40add561b6
Update README.md 2020-01-02 21:45:33 -05:00
Nathan Marz
123af0937e update release date 2019-10-13 23:07:26 -04:00
Nathan Marz
6e7d772755 update codox 2019-10-13 23:04:36 -04:00
Nathan Marz
134beac2a7 begin 1.1.4 2019-10-13 22:56:15 -04:00
Nathan Marz
86f8412ee4 update deploy 2019-10-13 22:56:04 -04:00
Nathan Marz
28ef42152a 1.1.3 2019-10-13 22:22:18 -04:00
Nathan Marz
ffe8130bf0 Merge branch 'master' of github.com:redplanetlabs/specter 2019-09-10 18:19:08 -04:00
Nathan Marz
9c7f6fb65e don't write class files for eval'd functions for inline path functions or protpath extensions. 2019-09-10 18:18:56 -04:00
Nathan Marz
029a33427f
Update README.md
Update links in README
2019-08-29 13:39:20 -04:00
nathanmarz
925e2e91d6 Merge branch 'master' of github.com:nathanmarz/specter 2019-08-27 20:48:58 -04:00
nathanmarz
29ee91d01e add CONTRIBUTING.md 2019-08-27 20:48:49 -04:00
Nathan Marz
b7ff05bf54
Update .travis.yml 2019-08-24 19:19:34 -04:00
Nathan Marz
ae24856ec3
Update travis link 2019-08-24 19:14:08 -04:00
Nathan Marz
23f4e1a370
Update README.md 2019-01-29 14:25:15 -05:00
Nathan Marz
e38a2561d8
Merge pull request #275 from xificurC/master
use criterium for benchmarks
2018-12-13 21:23:03 -05:00
Peter Nagy (NPE)
fe1deb5195 use criterium for benchmarks, output JVM and clojure version 2018-12-13 12:54:28 +01:00
nathanmarz
7790213b16 update changelog 2018-12-03 14:30:28 -05:00
nathanmarz
8764a4b2b6 fix throw-illegal in cljs code 2018-12-03 14:29:58 -05:00
nathanmarz
c65b31181a update changelog 2018-11-01 21:40:57 -04:00
nathanmarz
f13b5db08a begin 1.1.3 2018-11-01 21:39:09 -04:00
nathanmarz
6c3253f16a 1.1.2 2018-11-01 21:38:17 -04:00
nathanmarz
e7abb2b538 fix handling of subvector paths in cljs 2018-06-26 08:39:28 -04:00
nathanmarz
3536e3c461 update class-constant-test for cljs 2018-06-12 10:48:56 -04:00
nathanmarz
350c8b857f Fix inline compiler symbol handling so class references can be used as constants within paths 2018-06-12 10:09:11 -04:00
nathanmarz
798cda211f eliminate reflection warning 2018-05-18 13:45:20 -04:00
18 changed files with 532 additions and 241 deletions

37
.github/workflows/ci.yml vendored Normal file
View 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
View file

@ -11,3 +11,5 @@ pom.xml.asc
.lein-failures .lein-failures
.cljs_node_repl .cljs_node_repl
out/ out/
.cpcache
.cache

View file

@ -1,8 +0,0 @@
language: clojure
lein: 2.7.1
cache:
directories:
- $HOME/.m2
script:
- lein test
- lein doo node test-build once

View file

@ -1,3 +1,21 @@
## 1.1.4
* 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 ## 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. * 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.

9
CONTRIBUTING.md Normal file
View 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.

View file

@ -1,7 +1,7 @@
# Running Clojure tests # Running Clojure tests
``` ```
lein cleantest lein do clean, test
``` ```
# Running ClojureScript tests # Running ClojureScript tests
@ -10,3 +10,16 @@ lein cleantest
lein javac lein javac
lein doo node test-build once 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"
```

View file

@ -1,10 +1,10 @@
# Specter [![Build Status](https://secure.travis-ci.org/nathanmarz/specter.png?branch=master)](http://travis-ci.org/nathanmarz/specter) # Specter
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 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 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. 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/nathanmarz/specter/wiki/Specter's-inline-caching-implementation) to strip away the overhead of composition. 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). 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).
@ -84,23 +84,23 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org
- Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk) - 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. - 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.
- Screencast on Specter: [Understanding Specter](https://www.youtube.com/watch?v=rh5J4vacG98) - Screencast on Specter: [Understanding Specter](https://www.youtube.com/watch?v=rh5J4vacG98)
- 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. - 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/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. - 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/nathanmarz/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-Recursively) explains how to do precise and efficient recursive navigation with Specter.
- [This wiki page](https://github.com/nathanmarz/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. - [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/nathanmarz/specter/wiki/Cheat-Sheet) - [Cheat Sheet](https://github.com/redplanetlabs/specter/wiki/Cheat-Sheet)
- [API docs](http://nathanmarz.github.io/specter/) - [API docs](http://redplanetlabs.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. - 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'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. - [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/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/transients.cljc): This contains navigators for transient collections. - [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/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter. - [zipper.cljc](https://github.com/redplanetlabs/specter/blob/master/src/clj/com/rpl/specter/zipper.cljc): This integrates zipper-based navigation into Specter.
# Questions? # 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/). You can also find help in the #specter channel on [Clojurians](http://clojurians.net/).
@ -322,6 +322,22 @@ Specter supports ClojureScript! However, some of the differences between Clojure
# Future work # 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. - 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.
# 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 # License
Copyright 2015-2018 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.

View file

@ -1 +1 @@
1.1.1 1.1.5-SNAPSHOT

19
bb.edn Normal file
View 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*)}}}

View file

@ -9,7 +9,7 @@
:test-paths ["test", "target/test-classes"] :test-paths ["test", "target/test-classes"]
:auto-clean false :auto-clean false
:dependencies [[riddley "0.1.12"]] :dependencies [[riddley "0.1.12"]]
:plugins [[lein-codox "0.9.5"] :plugins [[lein-codox "0.10.7"]
[lein-doo "0.1.7"]] [lein-doo "0.1.7"]]
:codox {:source-paths ["target/classes" "src/clj"] :codox {:source-paths ["target/classes" "src/clj"]
:namespaces [com.rpl.specter :namespaces [com.rpl.specter
@ -31,8 +31,13 @@
:profiles {:dev {:dependencies :profiles {:dev {:dependencies
[[org.clojure/test.check "0.9.0"] [[org.clojure/test.check "0.9.0"]
[org.clojure/clojure "1.9.0"] [org.clojure/clojure "1.9.0"]
[org.clojure/clojurescript "1.10.126"]]} [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"]]}} :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"]}) :aliases {"deploy" ["do" "clean," "deploy" "clojars"]})

View file

@ -2,57 +2,31 @@
(:use [com.rpl.specter] (:use [com.rpl.specter]
[com.rpl.specter.transients]) [com.rpl.specter.transients])
(:require [clojure.walk :as walk] (:require [clojure.walk :as walk]
[com.rpl.specter.impl :as i])) [com.rpl.specter.impl :as i]
[criterium.core :as bench]))
;; run via `lein repl` with `(load-file "scripts/benchmarks.clj")`
(defn pretty-float5 [anum]
(format "%.5g" anum))
(defn pretty-float3 [anum] (defn pretty-float3 [anum]
(format "%.3g" anum)) (format "%.3g" anum))
(defn time-ms [amt afn] (defn mean [a-fn]
(let [start (System/nanoTime) (-> a-fn (bench/benchmark* {}) :mean first (* 1000000)))
_ (dotimes [_ amt] (afn))
end (System/nanoTime)]
(/ (- end start) 1000000.0)))
(defn compare-benchmark [afn-map]
(defn avg [numbers] (let [results (transform MAP-VALS mean afn-map)
(/ (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)
[[_ best-time] & _ :as sorted] (sort-by last results)] [[_ best-time] & _ :as sorted] (sort-by last results)]
(println "\nMean(us)\tvs best\t\tCode")
(println "\nAvg(ms)\t\tvs best\t\tCode")
(doseq [[k t] sorted] (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 & exprs]
(defmacro run-benchmark [name amt-per-iter & 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 {}))] (let [afn-map (->> exprs shuffle (map (fn [e] [`(quote ~e) `(fn [] ~e)])) (into {}))]
`(do `(do
(println "Benchmark:" ~name (str "(" ~amt-per-iter " iterations)")) (println "Benchmark:" ~name)
(compare-benchmark ~amt-per-iter ~afn-map) (compare-benchmark ~afn-map)
(println "\n********************************\n")))) (println "\n********************************\n"))))))
(defn specter-dynamic-nested-get [data a b c] (defn specter-dynamic-nested-get [data a b c]
(select-any (keypath a b c) data)) (select-any (keypath a b c) data))
@ -69,7 +43,7 @@
(let [data {:a {:b {:c 1}}} (let [data {:a {:b {:c 1}}}
p (comp-paths :a :b :c)] 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 [:a :b :c] data)
(select-any (keypath :a :b :c) data) (select-any (keypath :a :b :c) data)
(select-one [:a :b :c] data) (select-one [:a :b :c] data)
@ -86,7 +60,7 @@
(let [data {:a {:b {:c 1}}}] (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) (assoc-in data [:a :b :c] 1)
(setval [:a :b :c] 1 data))) (setval [:a :b :c] 1 data)))
@ -103,7 +77,7 @@
(my-update m3 :c afn)))))) (my-update m3 :c afn))))))
(let [data {:a {:b {:c 1}}}] (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) (update-in data [:a :b :c] inc)
(transform [:a :b :c] inc data) (transform [:a :b :c] inc data)
(manual-transform data inc))) (manual-transform data inc)))
@ -135,14 +109,14 @@
(let [data '(1 2 3 4 5)] (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) (transform ALL inc data)
(doall (sequence (map inc) data)) (doall (sequence (map inc) data))
(reverse (into '() (map inc) data)) (reverse (into '() (map inc) data))
)) ))
(let [data {:a 1 :b 2 :c 3 :d 4}] (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)])) (into {} (for [[k v] data] [k (inc v)]))
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data) (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)) (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 {}))] (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)])) (into {} (for [[k v] data] [k (inc v)]))
(reduce-kv (fn [m k v] (assoc m k (inc v))) {} data) (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)) (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]] (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) (first data)
(select-any ALL data) (select-any ALL data)
(select-any FIRST data) (select-any FIRST data)
@ -182,7 +156,7 @@
)) ))
(let [data [1 2 3 4 5]] (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)) (vec (map inc data))
(mapv inc data) (mapv inc data)
(transform ALL inc data) (transform ALL inc data)
@ -190,7 +164,7 @@
(let [data [1 2 3 4 5 6 7 8 9 10]] (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)) (doall (filter even? data))
(filterv even? data) (filterv even? data)
(select [ALL even?] data) (select [ALL even?] data)
@ -200,7 +174,7 @@
(let [data [{:a 2 :b 2} {:a 1} {:a 4} {:a 6}] (let [data [{:a 2 :b 2} {:a 1} {:a 4} {:a 6}]
xf (comp (map :a) (filter even?))] 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) (select [ALL :a even?] data)
(->> data (mapv :a) (filter even?) doall) (->> data (mapv :a) (filter even?) doall)
(into [] (comp (map :a) (filter even?)) data) (into [] (comp (map :a) (filter even?)) data)
@ -209,14 +183,13 @@
(let [v (vec (range 1000))] (let [v (vec (range 1000))]
(run-benchmark "Append to a large vector" (run-benchmark "Append to a large vector"
2000000
(setval END [1] v) (setval END [1] v)
(setval AFTER-ELEM 1 v) (setval AFTER-ELEM 1 v)
(reduce conj v [1]) (reduce conj v [1])
(conj v 1))) (conj v 1)))
(let [data [1 2 3 4 5 6 7 8 9 10]] (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)) (vec (cons 0 data))
(setval BEFORE-ELEM 0 data) (setval BEFORE-ELEM 0 data)
(into [0] data) (into [0] data)
@ -244,7 +217,6 @@
(let [data [1 2 [[3]] [4 6 [7 [8]] 10]]] (let [data [1 2 [[3]] [4 6 [7 [8]] 10]]]
(run-benchmark "update every value in a tree (represented with vectors)" (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) (walk/postwalk (fn [e] (if (and (number? e) (even? e)) (inc e) e)) data)
(transform [(walker number?) even?] inc data) (transform [(walker number?) even?] inc data)
(transform [TreeValues even?] inc data) (transform [TreeValues even?] inc data)
@ -254,7 +226,6 @@
(let [toappend (range 1000)] (let [toappend (range 1000)]
(run-benchmark "transient comparison: building up vectors" (run-benchmark "transient comparison: building up vectors"
8000
(reduce (fn [v i] (conj v i)) [] toappend) (reduce (fn [v i] (conj v i)) [] toappend)
(reduce (fn [v i] (conj! v i)) (transient []) toappend) (reduce (fn [v i] (conj! v i)) (transient []) toappend)
(setval END toappend []) (setval END toappend [])
@ -262,7 +233,6 @@
(let [toappend (range 1000)] (let [toappend (range 1000)]
(run-benchmark "transient comparison: building up vectors one at a time" (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)) [] toappend)
(reduce (fn [v i] (conj! v i)) (transient []) toappend) (reduce (fn [v i] (conj! v i)) (transient []) toappend)
(reduce (fn [v i] (setval END [i] v)) [] toappend) (reduce (fn [v i] (setval END [i] v)) [] toappend)
@ -273,7 +243,6 @@
tdata (transient data) tdata (transient data)
tdata2 (transient data)] tdata2 (transient data)]
(run-benchmark "transient comparison: assoc'ing in vectors" (run-benchmark "transient comparison: assoc'ing in vectors"
2500000
(assoc data 600 0) (assoc data 600 0)
(assoc! tdata 600 0) (assoc! tdata 600 0)
(setval (keypath 600) 0 data) (setval (keypath 600) 0 data)
@ -284,7 +253,6 @@
tdata (transient data) tdata (transient data)
tdata2 (transient data)] tdata2 (transient data)]
(run-benchmark "transient comparison: assoc'ing in maps" (run-benchmark "transient comparison: assoc'ing in maps"
1500000
(assoc data 600 0) (assoc data 600 0)
(assoc! tdata 600 0) (assoc! tdata 600 0)
(setval (keypath 600) 0 data) (setval (keypath 600) 0 data)
@ -298,31 +266,27 @@
[k (rand)])) [k (rand)]))
tdata (transient data)] tdata (transient data)]
(run-benchmark "transient comparison: submap" (run-benchmark "transient comparison: submap"
150000
(transform (submap [600 700]) modify-submap data) (transform (submap [600 700]) modify-submap data)
(transform (submap! [600 700]) modify-submap tdata))) (transform (submap! [600 700]) modify-submap tdata)))
(let [data {:x 1} (let [data {:x 1}
meta-map {:my :metadata}] meta-map {:my :metadata}]
(run-benchmark "set metadata" (run-benchmark "set metadata"
1500000
(with-meta data meta-map) (with-meta data meta-map)
(setval META meta-map data))) (setval META meta-map data)))
(let [data (with-meta {:x 1} {:my :metadata})] (let [data (with-meta {:x 1} {:my :metadata})]
(run-benchmark "get metadata" (run-benchmark "get metadata"
15000000
(meta data) (meta data)
(select-any META data))) (select-any META data)))
(let [data (with-meta {:x 1} {:my :metadata})] (let [data (with-meta {:x 1} {:my :metadata})]
(run-benchmark "vary metadata" (run-benchmark "vary metadata"
800000
(vary-meta data assoc :y 2) (vary-meta data assoc :y 2)
(setval [META :y] 2 data))) (setval [META :y] 2 data)))
(let [data (range 1000)] (let [data (range 1000)]
(run-benchmark "Traverse into a set" 5000 (run-benchmark "Traverse into a set"
(set data) (set data)
(set (select ALL data)) (set (select ALL data))
(into #{} (traverse ALL data)) (into #{} (traverse ALL data))
@ -334,18 +298,18 @@
(defn mult-10 [v] (* 10 v)) (defn mult-10 [v] (* 10 v))
(let [data [1 2 3 4 5 6 7 8 9]] (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)) (->> data (transform [ALL even?] mult-10) (transform [ALL odd?] dec))
(multi-transform [ALL (multi-path [even? (terminal mult-10)] [odd? (terminal dec)])] data))) (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]]] (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)) (->> 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))) (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}] (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 {} (into {}
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v])) (map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
data) data)
@ -355,7 +319,7 @@
(let [data (->> (for [i (range 1000)] [(keyword (str i)) i]) (into {}))] (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 {} (into {}
(map (fn [[k v]] [(keyword (str *ns*) (name k)) v])) (map (fn [[k v]] [(keyword (str *ns*) (name k)) v]))
data) data)
@ -370,7 +334,24 @@
(i/walk-until afn next-fn structure))) (i/walk-until afn next-fn structure)))
(let [data {:a [1 2 {:c '(3 4) :d {:e [1 2 3] 7 8 9 10}}]}] (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 number?) inc data)
(transform (walker-old 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)))

View file

@ -1,4 +1,8 @@
#!/bin/bash #!/bin/bash
`lein javac` lein javac
java -server -XX:MaxPermSize=128m -XX:MaxInlineSize=100 -cp `lein classpath` clojure.main scripts/benchmarks.clj lein version
echo
lein show-profiles bench
echo
java -server -XX:MaxInlineSize=100 -cp "$(lein with-profile bench classpath)" clojure.main scripts/benchmarks.clj "$@"

View file

@ -115,7 +115,32 @@
(providepath ~self-sym ~path) (providepath ~self-sym ~path)
~self-sym))))) ~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 (defn- name-with-attributes
"To be used in macro definitions. "To be used in macro definitions.
Handles optional docstrings and attribute maps for a name to be defined Handles optional docstrings and attribute maps for a name to be defined
@ -126,20 +151,31 @@
macro argument list. The return value is a vector containing the name macro argument list. The return value is a vector containing the name
with its extended metadata map and the list of unprocessed macro with its extended metadata map and the list of unprocessed macro
arguments." arguments."
[name macro-args] [name fdecl]
(let [[docstring macro-args] (if (string? (first macro-args)) (let [m (if (string? (first fdecl))
[(first macro-args) (next macro-args)] {:doc (first fdecl)}
[nil macro-args]) {})
[attr macro-args] (if (map? (first macro-args))
[(first macro-args) (next macro-args)] fdecl (if (string? (first fdecl))
[{} macro-args]) (next fdecl)
attr (if docstring fdecl)
(assoc attr :doc docstring) m (if (map? (first fdecl))
attr) (conj m (first fdecl))
attr (if (meta name) m)
(conj (meta name) attr) fdecl (if (map? (first fdecl))
attr)] (next fdecl)
[(with-meta name attr) macro-args])) 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] (defmacro dynamicnav [& args]
`(vary-meta (wrap-dynamic-nav (fn ~@args)) assoc :dynamicnav true)) `(vary-meta (wrap-dynamic-nav (fn ~@args)) assoc :dynamicnav true))
@ -166,7 +202,10 @@
embed (i/maybe-direct-nav path (-> s meta :direct-nav))] embed (i/maybe-direct-nav path (-> s meta :direct-nav))]
`(com.rpl.specter.impl/->LocalSym ~path (quote ~embed))) `(com.rpl.specter.impl/->LocalSym ~path (quote ~embed)))
;; var-get doesn't work in cljs, so capture the val in the macro instead ;; 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) (i/fn-invocation? path)
@ -453,7 +492,8 @@
params (-> protpath-prot :sigs first last :arglists first)] params (-> protpath-prot :sigs first last :arglists first)]
(doseq [[atype path-code] extensions] (doseq [[atype path-code] extensions]
(extend atype protpath-prot (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 (defmacro extend-protocolpath
"Used in conjunction with `defprotocolpath`. See [[defprotocolpath]]." "Used in conjunction with `defprotocolpath`. See [[defprotocolpath]]."
@ -970,11 +1010,10 @@
NONE) NONE)
(transform* [this vals structure next-fn] (transform* [this vals structure next-fn]
(let [v (next-fn vals NONE)] (let [v (next-fn vals NONE)]
(if (identical? NONE v) (if
(identical? NONE v)
structure structure
;; TODO: make a more efficient impl (n/insert-before-idx structure index v)))))
(setval (srange index index) [v] structure)
))))
(defrichnav (defrichnav
^{:doc "Navigates to the index of the sequence if within 0 and size. Transforms move element ^{:doc "Navigates to the index of the sequence if within 0 and size. Transforms move element
@ -1269,7 +1308,7 @@
ns (namespace structure)] ns (namespace structure)]
(cond (keyword? structure) (keyword ns new-name) (cond (keyword? structure) (keyword ns new-name)
(symbol? structure) (symbol 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"} (defnav ^{:doc "Navigates to the namespace portion of the keyword or symbol"}
@ -1282,7 +1321,8 @@
new-ns (next-fn (namespace structure))] new-ns (next-fn (namespace structure))]
(cond (keyword? structure) (keyword new-ns name) (cond (keyword? structure) (keyword new-ns name)
(symbol? structure) (symbol 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 (defdynamicnav

View file

@ -1,7 +1,8 @@
(ns com.rpl.specter.impl (ns com.rpl.specter.impl
#?(:cljs (:require-macros #?(:cljs (:require-macros
[com.rpl.specter.util-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 ;; 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 ;; private var in cljs.core (in this case `NONE`, added as private var to
;; cljs.core with 1.9.562) ;; cljs.core with 1.9.562)
@ -14,10 +15,11 @@
#?(:clj [clojure.pprint :as pp]) #?(:clj [clojure.pprint :as pp])
[clojure.string :as s] [clojure.string :as s]
[clojure.walk :as walk] [clojure.walk :as walk]
#?(:clj [riddley.walk :as riddley])) #?(:bb [clojure.walk :as riddley]
:clj [riddley.walk :as riddley]))
#?(:clj (:import [com.rpl.specter Util MutableCell])))
#?@(:bb []
:clj [(:import [com.rpl.specter Util MutableCell])]))
(def NONE ::NONE) (def NONE ::NONE)
@ -49,21 +51,6 @@
([a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 & r] v))) ([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 ;; need to get the expansion function like this so that
;; this code compiles in a clojure environment where cljs.analyzer ;; this code compiles in a clojure environment where cljs.analyzer
;; namespace does not exist ;; namespace does not exist
@ -85,7 +72,7 @@
:cljs :cljs
(defn clj-macroexpand-all [form] (defn clj-macroexpand-all [form]
(throw-illegal "not implemented"))) (throw (ex-info "not implemented" {}))))
#?( #?(
@ -94,9 +81,11 @@
:cljs :cljs
(defn intern* [ns name val] (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 :clj
(defmacro fast-object-array [i] (defmacro fast-object-array [i]
`(com.rpl.specter.Util/makeObjectArray ~i))) `(com.rpl.specter.Util/makeObjectArray ~i)))
@ -115,7 +104,8 @@
(if (= platform :cljs) (if (= platform :cljs)
`(p/select* ~this ~@args) `(p/select* ~this ~@args)
`(let [~hinted ~this] `(let [~hinted ~this]
(.select* ~hinted ~@args))))) (#?(:bb p/select*
:clj .select*) ~hinted ~@args)))))
:cljs :cljs
(defn exec-select* [this vals structure next-fn] (defn exec-select* [this vals structure next-fn]
(p/select* ^not-native this vals structure next-fn))) (p/select* ^not-native this vals structure next-fn)))
@ -129,7 +119,8 @@
(if (= platform :cljs) (if (= platform :cljs)
`(p/transform* ~this ~@args) `(p/transform* ~this ~@args)
`(let [~hinted ~this] `(let [~hinted ~this]
(.transform* ~hinted ~@args))))) (#?(:bb p/transform*
:clj .transform*) ~hinted ~@args)))))
:cljs :cljs
(defn exec-transform* [this vals structure next-fn] (defn exec-transform* [this vals structure next-fn]
@ -148,7 +139,9 @@
(defn- coerce-object [this] (defn- coerce-object [this]
(cond (rich-nav? this) this (cond (rich-nav? this) this
(satisfies? p/ImplicitNav this) (p/implicit-nav 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 (defprotocol CoercePath
@ -177,7 +170,7 @@
(coerce-path (vec this)))) (coerce-path (vec this))))
#?(:cljs cljs.core/Subvec) #?(:cljs cljs.core/Subvec)
#?(:cljs (coerce-path [this] #?(:cljs (coerce-path [this]
(coerce-path (vec this)))) (coerce-path (into [] this))))
#?(:clj Object :cljs default) #?(:clj Object :cljs default)
(coerce-path [this] (coerce-path [this]
@ -226,13 +219,19 @@
(set_cell [cell x]))) (set_cell [cell x])))
#?(:cljs #?(:bb
(defrecord MutableCell [x])
:cljs
(deftype MutableCell [^:volatile-mutable q] (deftype MutableCell [^:volatile-mutable q]
PMutableCell PMutableCell
(set_cell [this x] (set! q x)))) (set_cell [this x] (set! q x))))
#?( #?(:bb
(defn mutable-cell
([] (mutable-cell nil))
([v] (MutableCell. (volatile! v))))
:clj :clj
(defn mutable-cell (defn mutable-cell
([] (mutable-cell nil)) ([] (mutable-cell nil))
@ -244,7 +243,10 @@
([init] (MutableCell. init)))) ([init] (MutableCell. init))))
#?( #?(:bb
(defn set-cell! [^MutableCell c v]
(vreset! (:x c) v))
:clj :clj
(defn set-cell! [^MutableCell c v] (defn set-cell! [^MutableCell c v]
(.set c v)) (.set c v))
@ -254,7 +256,10 @@
(set_cell cell val))) (set_cell cell val)))
#?( #?(:bb
(defn get-cell [^MutableCell c]
@(:x c))
:clj :clj
(defn get-cell [^MutableCell c] (defn get-cell [^MutableCell c]
(.get c)) (.get c))
@ -303,7 +308,9 @@
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce) (reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
(#?(:clj reduce :cljs -reduce) (#?(:clj reduce :cljs -reduce)
[this afn] [this afn]
(#?(:clj .reduce :cljs -reduce) this afn (afn))) #?(:bb (reduce afn (afn) this)
:default
(#?(:clj .reduce :cljs -reduce) this afn (afn))))
(#?(:clj reduce :cljs -reduce) (#?(:clj reduce :cljs -reduce)
[this afn start] [this afn start]
(let [cell (mutable-cell start)] (let [cell (mutable-cell start)]
@ -319,15 +326,31 @@
(get-cell cell) (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] (defn do-compiled-traverse [apath structure]
(let [traverser (do-compiled-traverse* apath structure)] (let [traverser (do-compiled-traverse* apath structure)]
(reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce) (reify #?(:clj clojure.lang.IReduce :cljs cljs.core/IReduce)
(#?(:clj reduce :cljs -reduce) (#?(:clj reduce :cljs -reduce)
[this afn] [this afn]
(#?(:clj .reduce :cljs -reduce) this afn (afn))) #?(:bb (reduce afn (afn) this)
:default
(#?(:clj .reduce :cljs -reduce) this afn (afn))))
(#?(:clj reduce :cljs -reduce) (#?(:clj reduce :cljs -reduce)
[this afn start] [this afn start]
(let [res (#?(:clj .reduce :cljs -reduce) traverser afn start)] (let [res (call-reduce-interface traverser afn start)]
(unreduced res) (unreduced res)
))))) )))))
@ -361,7 +384,8 @@
(let [curr (get-cell res)] (let [curr (get-cell res)]
(if (identical? curr NONE) (if (identical? curr NONE)
(set-cell! res structure) (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) (compiled-traverse* path result-fn structure)
(let [ret (get-cell res)] (let [ret (get-cell res)]
@ -376,11 +400,12 @@
(let [curr (get-cell res)] (let [curr (get-cell res)]
(if (identical? curr NONE) (if (identical? curr NONE)
(set-cell! res structure) (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) (compiled-traverse* path result-fn structure)
(let [ret (get-cell res)] (let [ret (get-cell res)]
(if (identical? NONE ret) (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))) ret)))
@ -691,16 +716,18 @@
(preserve-map magic-precompilation* o) (preserve-map magic-precompilation* o)
(instance? VarUse o) (instance? VarUse o)
(if (dynamic-var? (:avar o)) (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 (->DynamicVal (maybe-direct-nav
(:sym o) (:sym o)
(or (-> o :avar direct-nav?) (or (direct-nav? v)
(-> o :sym direct-nav?)))) (-> o :sym direct-nav?))))
(maybe-direct-nav (maybe-direct-nav
(:val o) (:val o)
(or (-> o :avar direct-nav?) (or (and v (direct-nav? v))
(-> o :sym direct-nav?) (-> o :sym direct-nav?)
(-> o :val direct-nav?)))) (-> o :val direct-nav?)))))
(instance? LocalSym o) (instance? LocalSym o)
(->DynamicVal (:sym o)) (->DynamicVal (:sym o))
@ -796,7 +823,8 @@
(defn dynamic-val-code [code possible-params] (defn dynamic-val-code [code possible-params]
(let [[i] (keep-indexed (fn [i v] (if (= v code) i)) possible-params)] (let [[i] (keep-indexed (fn [i v] (if (= v code) i)) possible-params)]
(if (nil? i) (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 (maybe-direct-nav
(->LocalParam i) (->LocalParam i)
(direct-nav? code))))) (direct-nav? code)))))
@ -891,6 +919,8 @@
(if (fn? e) (re-find #" .*" (pr-str e)) e)) (if (fn? e) (re-find #" .*" (pr-str e)) e))
o))) o)))
(def ^:dynamic *path-compile-files* false)
#?(:clj #?(:clj
(defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-param] (defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-param]
(let [code `(fn [~@used-locals-list] ~resolved-code) (let [code `(fn [~@used-locals-list] ~resolved-code)
@ -899,7 +929,11 @@
(println "Produced code:") (println "Produced code:")
(pp/pprint code) (pp/pprint code)
(println)) (println))
(binding [*ns* ns] (eval+ code)))) (binding [*ns* ns
*compile-files* (if *path-compile-files*
*compile-files*
false)]
(eval+ code))))
:cljs :cljs
(defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-params] (defn mk-dynamic-path-maker [resolved-code ns-str used-locals-list possible-params]
@ -962,9 +996,9 @@
(defn- multi-transform-error-fn [& nav] (defn- multi-transform-error-fn [& nav]
(throw-illegal (throw
"All navigation in multi-transform must end in 'terminal' " (ex-info "All navigation in multi-transform must end in 'terminal' navigators"
"navigators. Instead navigated to: " nav)) {:nav nav})))
(defn compiled-multi-transform* [path structure] (defn compiled-multi-transform* [path structure]
(compiled-transform* path multi-transform-error-fn structure)) (compiled-transform* path multi-transform-error-fn structure))

View file

@ -6,8 +6,8 @@
(defn- determine-params-impls [impls] (defn- determine-params-impls [impls]
(let [grouped (->> impls (map (fn [[n & body]] [n body])) (into {}))] (let [grouped (->> impls (map (fn [[n & body]] [n body])) (into {}))]
(if-not (= #{'select* 'transform*} (-> grouped keys set)) (if-not (= #{'select* 'transform*} (-> grouped keys set))
(i/throw-illegal "defnav must implement select* and transform*, instead got " (throw (ex-info "defnav must implement select* and transform*"
(keys grouped))) {:methods (keys grouped)})))
grouped)) grouped))
@ -39,11 +39,16 @@
(let [helpers (for [[mname [_ & mparams] & mbody] impls] (let [helpers (for [[mname [_ & mparams] & mbody] impls]
`(defn ~(helper-name name mname) [~@params ~@mparams] ~@mbody)) `(defn ~(helper-name name mname) [~@params ~@mparams] ~@mbody))
decls (for [[mname & _] impls] 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 `(do
~@decls ~@decls
~@helpers ~@helpers
(def ~name (nav ~params ~@impls))))) (def ~name-with-meta (nav ~params ~@impls)))))
(defmacro defrichnav [name 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))))

View file

@ -8,7 +8,8 @@
#?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]] #?(:clj (:use [com.rpl.specter.macros :only [defnav defrichnav]]
[com.rpl.specter.util-macros :only [doseqres]])) [com.rpl.specter.util-macros :only [doseqres]]))
(:require [com.rpl.specter.impl :as i] (:require [com.rpl.specter.impl :as i]
#?(:clj [clojure.core.reducers :as r]))) #?@(:bb []
:clj [[clojure.core.reducers :as r]])))
(defn not-selected?* (defn not-selected?*
@ -95,6 +96,13 @@
(filter not-NONE?)) (filter not-NONE?))
structure)) 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) #?(:clj clojure.lang.PersistentHashSet :cljs cljs.core/PersistentHashSet)
(all-transform [structure next-fn] (all-transform [structure next-fn]
(into #{} (into #{}
@ -103,7 +111,10 @@
structure)) structure))
#?(:clj clojure.lang.PersistentArrayMap) #?(: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] (all-transform [structure next-fn]
(let [k-it (.keyIterator structure) (let [k-it (.keyIterator structure)
v-it (.valIterator structure) v-it (.valIterator structure)
@ -185,10 +196,13 @@
:else :else
(->> structure #?(:bb (into empty-structure
(comp (map next-fn) (filter not-NONE?))
structure)
:clj (->> structure
(r/map next-fn) (r/map next-fn)
(r/filter not-NONE?) (r/filter not-NONE?)
(into empty-structure)))))) (into empty-structure)))))))
#?(:cljs default) #?(:cljs default)
@ -255,7 +269,10 @@
#?(:clj clojure.lang.PersistentArrayMap) #?(: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] (map-vals-transform [structure next-fn]
(let [k-it (.keyIterator structure) (let [k-it (.keyIterator structure)
v-it (.valIterator structure) v-it (.valIterator structure)
@ -282,7 +299,10 @@
array array
)] )]
(clojure.lang.PersistentArrayMap. 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] (map-keys-transform [structure next-fn]
(let [k-it (.keyIterator structure) (let [k-it (.keyIterator structure)
v-it (.valIterator structure) v-it (.valIterator structure)
@ -449,6 +469,24 @@
(prepend-one [structure elem] (prepend-one [structure elem]
(into [elem] structure)) (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) #?(:clj Object :cljs default)
(append-all [structure elements] (append-all [structure elements]
@ -474,6 +512,9 @@
(defprotocol FastEmpty (defprotocol FastEmpty
(fast-empty? [s])) (fast-empty? [s]))
(defprotocol InsertBeforeIndex
(insert-before-idx [aseq idx val]))
(defnav PosNavigator [getter updater] (defnav PosNavigator [getter updater]
(select* [this structure next-fn] (select* [this structure next-fn]
(if-not (fast-empty? structure) (if-not (fast-empty? structure)
@ -484,6 +525,17 @@
structure structure
(updater structure next-fn)))) (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] (defn- update-first-list [l afn]
(let [newf (afn (first l)) (let [newf (afn (first l))
@ -499,29 +551,7 @@
(if (nil? bl) '() bl) (if (nil? bl) '() bl)
(concat bl [lastl])))) (concat bl [lastl]))))
#?( (defn- update-first-vector [v afn]
:clj
(defn vec-count [^clojure.lang.IPersistentVector v]
(.length v))
:cljs
(defn vec-count [v]
(count v)))
#?(
:clj
(defn transient-vec-count [^clojure.lang.ITransientVector v]
(.count v))
:cljs
(defn transient-vec-count [v]
(count v)))
(extend-protocol UpdateExtremes
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(update-first [v afn]
(let [val (nth v 0) (let [val (nth v 0)
newv (afn val)] newv (afn val)]
(if (identical? i/NONE newv) (if (identical? i/NONE newv)
@ -529,7 +559,7 @@
(assoc v 0 newv) (assoc v 0 newv)
))) )))
(update-last [v afn] (defn- update-last-vector [v afn]
;; type-hinting vec-count to ^int caused weird errors with case ;; type-hinting vec-count to ^int caused weird errors with case
(let [c (int (vec-count v))] (let [c (int (vec-count v))]
(case c (case c
@ -549,6 +579,36 @@
(pop v) (pop v)
(assoc v i newe)))))) (assoc v i newe))))))
#?(:bb
(defn transient-vec-count [v]
(count v))
:clj
(defn transient-vec-count [^clojure.lang.ITransientVector v]
(.count v))
:cljs
(defn transient-vec-count [v]
(count v)))
(extend-protocol UpdateExtremes
#?(:clj clojure.lang.IPersistentVector :cljs cljs.core/PersistentVector)
(update-first [v afn]
(update-first-vector v afn))
(update-last [v afn]
(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) #?(:clj String :cljs string)
(update-first [s afn] (update-first [s afn]
(let [rests (subs s 1 (count s)) (let [rests (subs s 1 (count s))
@ -691,3 +751,36 @@
((:end-fn end-fn) structure start) ((:end-fn end-fn) structure start)
(end-fn structure) (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))))

View file

@ -65,4 +65,4 @@
`(defn ~'late-fn [~f ~args] `(defn ~'late-fn [~f ~args]
(case (count ~args) (case (count ~args)
~@(apply concat cases) ~@(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" {}))))))

View file

@ -1320,9 +1320,13 @@
(deftest traversed-test (deftest traversed-test
(is (= 10 (select-any (s/traversed s/ALL +) [1 2 3 4])))) (is (= 10 (select-any (s/traversed s/ALL +) [1 2 3 4]))))
(defn- predand= [pred ret v] (defn- predand= [pred v1 v2]
(and (pred ret) (and (pred v1)
(= ret v))) (pred v2)
(= v1 v2)))
(defn listlike? [v]
(or (list? v) (seq? v)))
(deftest nthpath-test (deftest nthpath-test
(is (predand= vector? [1 2 -3 4] (transform (s/nthpath 2) - [1 2 3 4]))) (is (predand= vector? [1 2 -3 4] (transform (s/nthpath 2) - [1 2 3 4])))
@ -1334,7 +1338,7 @@
(deftest remove-with-NONE-test (deftest remove-with-NONE-test
(is (predand= vector? [1 2 3] (setval [s/ALL nil?] s/NONE [1 2 nil 3 nil]))) (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 :a s/NONE {:a 1 :b 2})))
(is (= {:b 2} (setval (s/must :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]))) (is (predand= vector? [1 3] (setval (s/keypath 1) s/NONE [1 2 3])))
@ -1436,6 +1440,15 @@
(is (= "abq" (setval s/LAST "q" "abc"))) (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 (deftest regex-navigation-test
;; also test regexes as implicit navs ;; also test regexes as implicit navs
(is (= (select #"t" "test") ["t" "t"])) (is (= (select #"t" "test") ["t" "t"]))
@ -1454,11 +1467,11 @@
(deftest single-value-none-navigators-test (deftest single-value-none-navigators-test
(is (predand= vector? [1 2 3] (setval s/AFTER-ELEM 3 [1 2]))) (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= listlike? '(1 2 3) (setval s/AFTER-ELEM 3 '(1 2))))
(is (predand= list? '(1) (setval s/AFTER-ELEM 1 nil))) (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= 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= listlike? '(3 1 2) (setval s/BEFORE-ELEM 3 '(1 2))))
(is (predand= list? '(1) (setval s/BEFORE-ELEM 1 nil))) (is (predand= listlike? '(1) (setval s/BEFORE-ELEM 1 nil)))
(is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2}))) (is (= #{1 2 3} (setval s/NONE-ELEM 3 #{1 2})))
(is (= #{1} (setval s/NONE-ELEM 1 nil))) (is (= #{1} (setval s/NONE-ELEM 1 nil)))
) )
@ -1502,7 +1515,7 @@
[l (limit-size 10 (gen/not-empty (gen/list gen/int)))] [l (limit-size 10 (gen/not-empty (gen/list gen/int)))]
(let [newl (setval s/FIRST s/NONE l)] (let [newl (setval s/FIRST s/NONE l)]
(and (= newl (rest l)) (and (= newl (rest l))
(list? newl) (listlike? newl)
)))) ))))
(defspec remove-last-vector (defspec remove-last-vector
@ -1609,14 +1622,18 @@
(deftest before-index-test (deftest before-index-test
(let [data [1 2 3] (let [data [1 2 3]
datal '(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? [: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 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 :a 2 3] (setval (s/before-index 1) :a data)))
(is (predand= vector? [1 2 3 :a] (setval (s/before-index 3) :a data))) (is (predand= vector? [1 2 3 :a] (setval (s/before-index 3) :a data)))
(is (predand= list? '(:a 1 2 3) (setval (s/before-index 0) :a datal))) ; ensure inserting at index 0 in nil structure works, as in previous impl
(is (predand= list? '(1 :a 2 3) (setval (s/before-index 1) :a datal))) (is (predand= listlike? '(:a) (setval (s/before-index 0) :a nil)))
(is (predand= list? '(1 2 3 :a) (setval (s/before-index 3) :a datal))) (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 (deftest index-nav-test
@ -1629,12 +1646,12 @@
(is (predand= vector? [1 2 4 5 6 3] (setval (s/index-nav 2) 5 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= vector? [6 1 2 3 4 5] (setval (s/index-nav 5) 0 data)))
(is (predand= list? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal))) (is (predand= listlike? '(3 1 2 4 5 6) (setval (s/index-nav 2) 0 datal)))
(is (predand= list? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal))) (is (predand= listlike? '(1 3 2 4 5 6) (setval (s/index-nav 2) 1 datal)))
(is (predand= list? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal))) (is (predand= listlike? '(1 2 3 4 5 6) (setval (s/index-nav 2) 2 datal)))
(is (predand= list? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal))) (is (predand= listlike? '(1 2 4 5 3 6) (setval (s/index-nav 2) 4 datal)))
(is (predand= list? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal))) (is (predand= listlike? '(1 2 4 5 6 3) (setval (s/index-nav 2) 5 datal)))
(is (predand= list? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal))) (is (predand= listlike? '(6 1 2 3 4 5) (setval (s/index-nav 5) 0 datal)))
)) ))
(deftest indexed-vals-test (deftest indexed-vals-test
@ -1679,6 +1696,12 @@
{:a [{:b 2 :c 1}]}]))) {: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 #?(:clj
(do (do
(defprotocolpath FooPP) (defprotocolpath FooPP)