From bc4d1d0051410369a8ef56a4f9c71797e7747621 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 6 Jun 2016 12:34:21 -0400 Subject: [PATCH] updated readme --- README.md | 103 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6041d4f..8bce848 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,96 @@ # Specter [![Build Status](https://travis-ci.org/nathanmarz/specter.svg?branch=master)](http://travis-ci.org/nathanmarz/specter) -Specter is library for Clojure and ClojureScript for querying and manipulating arbitrarily complicated data structures very concisely. Its use cases range from transforming the values of a map to manipulating deeply nested data structures to performing sophisticated recursive tree transformations. Without Specter, writing these manipulations in Clojure manually is cumbersome and prone to error. +Specter is a Clojure and ClojureScript library that – because of its far-ranging applicability – is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly this is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs more rapidly and in a much more maintainable way. -Specter is fully extensible. At its core, it's just a protocol for how to navigate within a data structure. By extending this protocol, you can use Specter to navigate any data structure or object you have. +Here are three areas where Specter greatly improves Clojure programming: + +**Specter makes common tasks concise instead of cumbersome, and simple instead of complex** + +Example 1: Increment every value in a map + +```clojure +(def data {:a 1 :b 2 :c 3}) + +;; Manual Clojure: +(->> data (map (fn [[k v]] [k (inc v)])) (into {})) + +;; Specter: +(transform [ALL LAST] inc data) +``` + +Example 2: Increment every value nested within map of vector of maps + +```clojure +(def data {:a [{:aa 1 :bb 2} + {:cc 3}] + :b [{:dd 4}]}) + +;; Manual Clojure: +(defn map-vals [m afn] + (->> m (map (fn [[k v]] [k (afn v)])) (into {}))) +(map-vals data (fn [v] (mapv (fn [m] (map-vals m inc)) v))) + +;; Specter: +(def MAP-VALS (comp-paths ALL LAST)) +(transform [MAP-VALS ALL MAP-VALS] inc data) +``` + +**Specter is much faster than Clojure's limited built-in alternatives** + +Example 1: Specter's `select` is 27% faster than `get-in`: + +```clojure +(time + (dotimes [_ 10000000] + (get-in {:a {:b {:c 1}}} [:a :b :c]))) +"Elapsed time: 640.666 msecs" + +(time + (dotimes [_ 10000000] + (select [:a :b :c] {:a {:b {:c 1}}}))) +"Elapsed time: 470.167 msecs" +``` + +Example 2: Specter's `transform` is 6x faster than `update-in`: + +```clojure +(time + (dotimes [_ 10000000] + (update-in {:a {:b {:c 1}}} [:a :b :c] inc))) +"Elapsed time: 10662.014 msecs" + +(time + (dotimes [_ 10000000] + (transform [:a :b :c] inc {:a {:b {:c 1}}}))) +"Elapsed time: 1699.016 msecs" +``` + +**Specter makes sophisticated tasks – that are difficult to program manually – easy** + +Example 1: Reverse the order of even numbers in a tree (with order based on depth first search): + +```clojure +(transform (subselect (walker number?) even?) + reverse + [1 [[[2]] 3] 5 [6 [7 8]] 10]) +;; => [1 [[[10]] 3] 5 [8 [7 6]] 2] +``` + + +Example 2: Replace every continuous sequence of odd numbers with its sum: + +```clojure +(transform (continuous-subseqs odd?) + (fn [aseq] [(reduce + aseq)]) + [1 3 6 8 9 11 15 16] + ) +;; => [4 6 8 35 16] +``` + +This is just the tip of the iceberg. Because Specter is completely extensible, it can be used to navigate any data structure or object you have. All the navigators that come with Specter are built upon [very simple abstractions](https://github.com/nathanmarz/specter/blob/0.11.0/src/clj/com/rpl/specter/protocols.cljx). + +Even though Specter is so generic and flexible, its performance rivals hand-optimized code. Under the hood, Specter uses [advanced dynamic techniques](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) 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, `reduce-kv` on small maps, and `reduce-kv` in conjunction with transients on larger maps. You get the best of both worlds of elegance and performance. -Even though Specter is so generic and flexible, [its performance](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) rivals hand-optimized code. The only comparable functions in Clojure's core library are `get-in` and `update-in`. The equivalent Specter code is effectively identical (just different order of arguments), but Specter runs 30% faster than `get-in` and 5x faster than `update-in`. # Latest Version @@ -131,15 +217,6 @@ user> (transform [(srange 4 11) (filterer even?)] [0 1 2 3 10 5 8 7 6 9 4 11 12 13 14 15] ``` -Decrement every value in a map: - -```clojure -user> (transform [ALL LAST] - dec - {:a 1 :b 3}) -{:b 2 :a 0} -``` - Append [:c :d] to every subsequence that has at least two even numbers: ```clojure user> (setval [ALL @@ -226,7 +303,7 @@ The next examples demonstrate recursive navigation. Here's how to double all the ;; => [:a 1 [4 [[[3]]] :e] [8 5 [12 7]]] ``` -Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk: +Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk and is much more efficient than using `walker`: ```clojure (declarepath TreeValues)