Giter Site home page Giter Site logo

juji-io / editscript Goto Github PK

View Code? Open in Web Editor NEW
470.0 22.0 23.0 746 KB

A library to diff and patch Clojure/ClojureScript data structures

License: Eclipse Public License 1.0

Clojure 100.00%
clojure data diff patch data-diffing tree-diffing algorithm editscript clojurescript-data data-structures

editscript's Introduction

editscript logo

Editscript

๐Ÿ”ฆ Diff and patch for Clojure/Clojurescript data. ๐Ÿงฉ

editscript on cljdoc npm clojars editscript build status

๐Ÿ™‰ What is it?

Editscript is a library designed to extract the differences between two Clojure/Clojurescript data structures as an "editscript", which represents the minimal modification necessary to transform one to another.

Currently, this library can diff and patch any nested Clojure/Clojurescript data structures consisting of regular maps, vectors, lists, sets and values. Custom data can also be handled if you implement our protocols.

๐Ÿ˜† Status

This library is stable and has been in production use to power the core product of Juji for several years now. If you are also using Editscript, please drop a line at issue #17 so we may make a list of users here:

  • clerk uses Editscript to improves usability of synchronised atom by sending a minimal diff from the JVM to the browser, achieving 60fps sync for updates from the browser to the JVM and back.
  • Evident Systems uses Editscript as the main way of evaluating changes within the convergent reference type in their CRDT library, Converge.
  • microdata.no uses Editscript to sync client state to server so users can pick up their work where they left it.
  • Oche uses Editscript to sync game state between client and server.
  • Streetlinx uses Editscript to capture deltas to drive a newsfeed and generate alerts.

๐ŸŽ‰ Usage

See my Clojure/north 2020 Talk: Data Diffing Based Software Architecture Patterns.

(require '[editscript.core :as e])

;; Here are two pieces of data, a and b
(def a ["Hello word" 24 22 {:a [1 2 3]} 1 3 #{1 2}])
(def b ["Hello world" 24 23 {:a [2 3]} 1 3 #{1 2 3}])

;; compute the editscript between a and b using the default options
(def d (e/diff a b))

;; look at the editscript
(e/get-edits d)
;;==>
;; [[[0] :r "Hello world"] [[2] :r 23] [[3 :a 0] :-] [[6 3] :+ 3]]

;; diff using the quick algorithm and diff the strings by character
;; there are other string diff levels: :word, :line, or :none (default)
(def d-q (e/diff a b {:algo :quick :str-diff :character}))

(e/get-edits d-q)
;;=>
;; [[[0] :s [9 [:+ "l"] 1]] [[2] :r 23] [[3 :a 0] :-] [[6 3] :+ 3]]

;; get the edit distance, i.e. number of edits
(e/edit-distance d)
;;==> 4

;; get the size of the editscript, i.e. number of nodes
(e/get-size d)
;;==> 23

;; patch a with the editscript to get back b, so that
(= b (e/patch a d))
;;==> true
(= b (e/patch a d-q))
;;==> true

An Editscript contains a vector of edits, where each edit is a vector of two or three elements.

The first element of an edit is the path, similar to the path vector in the function call update-in. However, update-in only works for associative data structures (map and vector), whereas the editscript works for map, vector, list and set alike.

The second element of an edit is a keyword representing the edit operation, which is one of :- (deletion), :+ (addition), :r (data replacement) or :s (string edit).

For addition and replacement operation, the third element is the value of new data.

;; get the edits as a plain Clojure vector
(def v (e/get-edits d))

v
;;==>
;;[[[0] :r "Hello world"] [[2] :r 23] [[3 :a 0] :-] [[6 3] :+ 3]]

;; the plain Clojure vector can be passed around, stored, or modified as usual,
;; then be loaded back as a new EditScript
(def d' (e/edits->script v))

;; the new EditScript works the same as the old one
(= b (e/patch a d'))
;;==> true

๐Ÿ“— Documentation

Please see API Documentation for more details.

๐Ÿ›๏ธ Alternatives

Depending on your use cases, different libraries in this space may suit you needs better. The /bench folder of this repo contains a benchmark comparing the alternatives. The resulting charts of running the benchmark are included below:

Diff time benchmark Diff size benchmark

deep-diff2 applies Wu et al. 1990 [3] algorithm by first converting trees into linear structures. It is only faster than A* algorithm of Editscript. Its results are the largest in size. Although unable to achieve optimal tree diffing with this approach, it has some interesting use, e.g. visualization. So if you want to visualize the differences, use deep-diff2. This library does not do patch.

clojure.data/diff and differ are similar to the quick algorithm of Editscript, in that they all do a naive walk-through of the data, so the generated diff is not going to be optimal.

clojure.data/diff is good for detecting what part of the data have been changed and how. But it is slow and the results are also large. It does not do patch either.

differ looks very good by the numbers in the benchmark. It does patch, is fast and the results the smallest (for it doesn't record editing operators). Unfortunately, it cuts corners. It fails all the property based tests, even if the tests considered only vectors and maps. Use it if you understand its failing patterns and are able to avoid them in your data.

Editscript is designed for data diffing, e.g. data preservation and recovery, not for being looked at by humans. If speed is your primary concern, the quick algorithm of Editscript is the fastest among all the alternatives, and its diff size is reasonably small for the benchmarked data sets. If the diff size is your primary concern, A* algorithm is the only available option that guarantees optimal data size, but it is also the slowest.

โšก Diffing Algorithms

As mentioned, the library currently implements two diffing algorithms. The default algorithm produces diffs that are optimal in the number of editing operations and the resulting script size. A quick algorithm is also provided, which does not guarantee optimal results but is very fast.

A* diffing

This A* algorithm aims to achieve optimal diffing in term of minimal size of resulting editscript, useful for storage, query and restoration. This is an original algorithm that has some unique properties: unlike many other general tree differing algorithms such as Zhang & Shasha 1989 [4], our algorithm is structure preserving.

Roughly speaking, the edit distance is defined on sub-trees rather than nodes, such that the ancestor-descendant relationship and tree traversal order are preserved, and nodes in the original tree does not split or merge. These properties are useful for diffing and patching Clojure's immutable data structures because we want to leverage structure sharing and use identical? reference checks. The additional constraints also yield algorithms with better run time performance than the general ones. Finally, these constraints feel natural for a Clojure programmer.

The structure preserving properties were proposed in Lu 1979 [1] and Tanaka 1995 [2]. These papers describe diffing algorithms with O(|a||b|) time and space complexity. We designed an A* based algorithm to achieve some speedup. Instead of searching the whole editing graph, we typically search a portion of it along the diagonal.

The implementation is optimized for speed. Currently the algorithm spent most of its running time calculating the cost of next steps, perhaps due to the use of a very generic heuristic. A more specialized heuristic for our case should reduce the number of steps considered. For special cases of vectors and lists consisting of leaves only, we also use the quick algorithm below to enhance the speed.

Although much slower than the non-optimizing quick algorithm below, the algorithm is practical for common Clojure data that include lots of maps. Maps and sets do not incur the penalty of a large search space in the cases of vectors and lists. For a drawing data set, the diffing time is less than 3ms on a 2014 2.8 GHz Core i5 16GB MacBook Pro.

Quick diffing

This quick diffing algorithm simply does an one pass comparison of two trees so it is very fast. For sequence (vector and list) comparison, we implement Wu et al. 1990, an algorithm with O(NP) time complexity, where P is the number of deletions if b is longer than a. The same sequence diffing algorithm is also implemented in diffit. Using their benchmark, our implementation has slightly better performance due to more optimizations. Keep in mind that our algorithm also handles nested Clojure data structures. Compared with our A* algorithm, our quick algorithm can be up to two orders of magnitude faster.

The Wu algorithm does not have replacement operations, and assumes each edit has a unit cost. These do not work well for tree diffing. Consequently, the quick algorithm does not produce optimal results in term of script size. In principle, simply changing a pointer to point to b instead of a produces the fastest "diffing" algorithm of the world, but that is not very useful. The quick algorithm has a similar problem.

For instances, when consecutive deletions involving nested elements occur in a sequence, the generated editscript can be large. For example:

(def a [2 {:a 42} 3 {:b 4} {:c 29}])
(def b [{:a 5} {:b 5}])

(diff a b {:algo :quick})
;;==>
;;[[[0] :-]
;; [[0] :-]
;; [[0] :-]
;; [[0 :b] :-]
;; [[0 :a] :+ 5]
;; [[1 :c] :-]
;; [[1 :b] :+ 5]]

(diff a b)
;;==>
;; [[[] :r [{:a 5} {:b 5}]]]

In this case, the quick algorithm seems to delete the original and then add new ones back. The reason is that the quick algorithm does not drill down (i.e. do replacement) at the correct places. It currently drills down wherever it can. In this particular case, replacing the whole thing produces a smaller diff. An optimizing algorithm is needed if minimal diffs are desired.

๐Ÿš‰ Platform

The library supports JVM Clojure and Clojurescript. The later has been tested with node, nashorn, chrome, safari, firefox and lumo. E.g. run our test suite:

# Run Clojure tests
lein test

# Run Clojurescript tests on node.js
lein doo node

# Run Clojurescript tests on chrome
lein doo chrome browser once

๐Ÿ’ก Rationale

At Juji, we send changes of UI states back to server for persistence see blog post. Such a use case requires a good diffing library for nested Clojure data structures to avoid overwhelming our storage systems. I have not found such a library in Clojure ecosystem, so I implemented my own. Hopefully this little library could be of some use to further enhance the Clojure's unique strength of Data-Oriented Programming.

Editscript is designed with stream processing in mind. An editscript should be conceptualized as a chunk in a potentially endless stream of changes. Individual editscripts can combine (concatenate) into a larger edistscript. I consider editscript as a part of a larger data-oriented effort, that tries to elevate the level of abstraction of data from the granularity of characters, bytes or lines to that of maps, sets, vectors, and lists. So instead of talking about change streams in bytes, we can talk about change streams in term of higher level data structures.

๐ŸŽข Roadmap

There are a few things I have some interest in exploring with this library. Of course, ideas, suggestions and contributions are very welcome.

  • Further speed up of the algorithms, e.g. better heuristic, hashing, and so on.
  • Globally optimize an editscript stream.

๐Ÿ“— References

[1] Lu, S. 1979, A Tree-to-tree distance and its application to cluster analysis. IEEE Transactions on Pattern Analysis and Machine Intelligence. Vol. PAMI-1 No.2. p219-224

[2] Tanaka, E., 1995, A note on a tree-to-tree editing problem. International Journal of Pattern Recognition and Artificial Intelligence. p167-172

[3] Wu, S. et al., 1990, An O(NP) Sequence Comparison Algorithm, Information Processing Letters, 35:6, p317-23.

[4] Zhang, K. and Shasha, D. 1989, Simple fast algorithms for the editing distance between trees and related problems. SIAM Journal of Computing, 18:1245โ€“1262

License

Copyright ยฉ 2018-2024 Juji, Inc.

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

editscript's People

Contributors

arichiardi avatar huahaiy avatar lnostdal avatar outrovurt avatar the-alchemist avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

editscript's Issues

Handle atoms in the data structure

It is quite common to have a few atoms in the Clojure data structure. We should be able to handle them. e.g. maybe include a :deref step in the path.

Suboptimal edit?

In this diff:

(def a [:zero {:x :y}])
(def b [:zero {:a "a" :b "b" :c "c" :d "d" :e "e"}])

(diff a b))
 
=> [[[1] :-] [[1] :+ {:a "a", :b "b", :c "c", :d "d", :e "e"}]]

โ€ฆ it seems like removing and adding would be suboptimal. Wouldn't replacing be a smaller solution?

[[[1] :r {:a "a", :b "b", :c "c", :d "d", :e "e"}]]

Diff inside strings

Right now, we treat strings as atomic units, replacing them as a whole. This is quite wasteful storage wise. We should be able to diff inside the strings, e.g. maybe introduce a :str step.

WARNING: Use of undeclared Var goog.math.Long/getMaxValue at line 1 <cljs repl>

I'm trying to use editscript in a figwheel-main ClojureScript environment, however each time I try:

(require '[editscript.core])

I get the following warning message:

WARNING: Use of undeclared Var goog.math.Long/getMaxValue at line 1 <cljs repl>

I've traced the error to src/editscript/diff/a_star.cljc, specifically to

(ns ^:no-doc editscript.diff.a-star
  (:require ,,,
            #?(:cljs [goog.math.Long])))

and further down where it is used:

(defn- access-g
  [g cur]
  (get g cur #?(:clj Long/MAX_VALUE
                :cljs (goog.math.Long/getMaxValue))))

This usage seems to be throwing a warning, though to be honest I'm not entirely sure why. If I try to require the same in the figwheel REPL then evaluate the above, I get the same warning. However if I do the following:

(require '[goog.math.Long :refer [getMaxValue]])

it works fine.

My question is, is this something which even needs fixing, or can the said warning just be ignored or suppressed somehow?

Unable to resolve symbol: ->EditScript

I think the problem is that the ->EditScript function is not defined until after the deftype so it can't be used within the deftype body. You need to use the inter-op dot notation "EditScript."

Here's what I saw:

$ lein test
Exception in thread "main" java.lang.RuntimeException: Unable to resolve symbol: ->EditScript in this context, compiling:(editscript/edit.cljc:146:5)

My fix is this small diff...

diff --git a/src/editscript/edit.cljc b/src/editscript/edit.cljc
index da9666e..f79b60e 100644
--- a/src/editscript/edit.cljc
+++ b/src/editscript/edit.cljc
@@ -143,7 +143,7 @@

IEditScript
(combine [this that]

  • (->EditScript (into edits (get-edits that))
  • (EditScript. (into edits (get-edits that))

throws when patch and equality test issue

Hi, I finally got around testing this. Unfortunately, I'm running into some issues with this release]. When doing a trivial patch it throws.

(def edit-source {})
(def edit-dest {:x :hello-world})

"this throws"
(let [edits (editscript.edit/get-edits
              (editscript/diff edit-source edit-dest))]
  (editscript/patch edit-source (editscript.edit/edits->script edits))
  )

Throws with:

Execution error (AssertionError) at editscript.edit/edits->script (edit.cljc:198).
Assert failed: Not a vector of valid edits
(valid-edits? edits)

Also, I'm seeing some weird behavior with regard to equality:

(assert (= (editscript.edit/get-edits
                        (editscript/diff {} {}))
          (editscript.edit/get-edits
                        (editscript/diff {} {})))
  "this succeeds")

(def some-edits (editscript.edit/get-edits
                  (editscript/diff {} {})))

(assert (= some-edits (editscript.edit/get-edits
                        (editscript/diff {} {})))
  "this fails")

I created a full repro repo here: https://github.com/FreekPaans/editscript-repro/blob/master/src/editscript_test/core.clj. Also tested on Clojure 1.10.0, Java version was 11.

Originally posted by @FreekPaans in #8 (comment)

Limit diff to certain level of depth

Sometimes we do not need the detailed differences that are lower than certain level of depth, and it is sufficient to report the differences above the level.

Long runtime for the quick algorithm on some large data structures

At microdata.no we use editscript with the :quick diff algorithm to generate diff's between potentially large edn structures.

This usually works very well, but i have encountered a special case where the editscript/diff with the :quick algorithm is very slow.

It is hard to pinpoint exactly what it is in the data that causes this, as the compared files are about 2.4MB each, but here is a repo with two example files (which also contains code to run the diff):

https://github.com/sigvesn/editscript-quickdiff-example

  • each is a vector of about 200 maps
  • the map entries potentially contains a lot of data
  • almost each map in the vectors are equal, except for one boolean field :active with is present for all maps in old but only one map in new

diff using either the "non-quick" algorithm (or removing the :active field from the maps in old) runs in ~0.2-1 second

Using :quick true results in a runtime of ~7.5 minutes

diffing for nested MapEntry replaces the top level entry

This contains an update path:

(e/diff [1 [2 [3 4]]]
        [1 [2 [3 "๐Ÿค”๐Ÿค”๐Ÿค”"]]])
=> [[[1 1 1] :r "๐Ÿค”๐Ÿค”๐Ÿค”"]]

This replaces the top level MapEntry:

(ns user
  (:import (clojure.lang MapEntry)))

(e/diff (MapEntry/create 1 (MapEntry/create 2 (MapEntry/create 3 4)))
        (MapEntry/create 1 (MapEntry/create 2 (MapEntry/create 3 "๐Ÿค”๐Ÿค”๐Ÿค”"))))
=> [[[] :r [1 [2 [3 "๐Ÿค”๐Ÿค”๐Ÿค”"]]]]]

Running into this issue using https://github.com/metosin/malli#parsing-values where parts of the parse-tree are MapEntries.

Three-way merging

I want to implement a function (diff3 a o b) where o is common ancestor of a and b. The straightforward solution is combining editscripts of two diffs: (patch o (combine (diff o a) (diff o b))). But I stuck with detecting conflicts when concurrent updates happens at same place in a and b.

Is there any known solutions or algorithms of iterating over those editscripts and finding conflicting nodes?

Warning in ClojureScript: slurp

Loading in ClojureScript, I get the warning
WARNING: Use of undeclared Var cljs.core/slurp at line 23 target/cljsbuild/public/js/out/editscript/util/macros.cljc

MapEntry type crashes diff

EDIT: This happens in CLJS in the browser.

Case: You use clojure.core/find to produce a vector-like structure:

(def x {:a "a"
        :b "b"})

(find x :a) ;;  => [:a "a"]
;; The above is an instance of `MapEntry`

Map entries can mostly be used as a seq. But if you try to diff a structure that includes map entries, editscript will match them as associatives (vectors) and try to kv reduce them. That throws an exception.

editscript composition and optimization

First, I think this library is pretty interesting. I was wondering about one use case though: let's say you have entity A_t0 (where t is analogous to a time step) and you have an editscript e_0->1 to describe the transformation needed to get A_t0 to A_t1. If you capture an editscript for transformations at each time step (if there is a change), you'd have a collection of e, right? Then if you want to get the present state of A you could just concatenate all those editscripts together (to describe changes between t0 and tN). Have you tried this use case?

I wonder if at some point though, if the editscript gets large enough the patching process would slow down and it would be helpful to have some sort of editscript optimizer to reduce to the minimal editscript needed to get from At0 to AtN.

Examples in Readme don't produce correct edits

Using 0.5.8

The Readme shows this:

(def a [2 {:a 42} 3 {:b 4} {:c 29}])
(def b [{:a 5} {:b 5}])

(diff a b {:algo :quick})
;;==>
;;[[[0] :-]
;; [[0] :-]
;; [[0] :-]
;; [[0 :b] :-]
;; [[0 :a] :+ 5]
;; [[1 :c] :-]
;; [[1 :b] :+ 5]]

(diff a b)
;;==>
;;[[[0] :-]
;; [[0 :a] :r 5]
;; [[1] :-]
;; [[1 :b] :r 5]
;; [[2] :-]
]

However, diffing a and b on my system just produces this EditScript.

[[[] :r [{:a 5} {:b 5}]]]

The quick algo produces the expected result, but every time I diff lists with the A* algorithm, I get just a big replacement of the entire structure

How to restore the editscript?

Hi, I'm trying to persist the edit script so I can apply it at a later time. How would I go about doing that?

In essence what I want to do:

(patch a (get-edits (diff a b))

Thanks.

Support for structural editing operations

Hey, great library, thanks for your work!

I'd like to create edit scripts for Clojure code patches. Would it be feasible to add operations for structural editing? I'm thinking mainly about the following ones, tho there are some others.

  1. wrap: expr -> (expr)
  2. raise: (foo expr bar) -> expr
  3. slurp: (foo) expr -> (foo expr)
  4. barf: (foo expr) -> (foo) expr

Each could work for vectors and sets as well.

Example:

(e/diff
   '(do
      (let [a 6])
      (+ a 9))
   '(do
      (let [a 6]
        (+ a 10))))
-> [[[1 2] :+ (+ a 10)] 
    [[2] :-]]

would result in a cheaper sequence of operations:

[[[1] :slurp]
 [[1 2 2] :r 10]]

Another example:

  (e/diff
   '(do
      (large-expr))
   '(do
      (let [a 6]
       (large-expr))))
-> [[[1] :r (let [a 6] (large-expr))]]

would become something like

[[[1] :wrap :list]
 [[1 0] :+ let]
 [[1 1] :+ [a 6]]]

Repeated diffs applications on a vector crashes.

This seems to be caused by https://clojure.atlassian.net/browse/CLJ-2065, but just documenting it here

(def current-state (atom [:div]))
=> #'ssr/current-state
(println (type @current-state))
clojure.lang.PersistentVector
=> nil
(defn render
  [new]
  (hiccup/html
   (swap! current-state
          (fn [html-1]
            (let [diff (editscript/diff html-1 new)]
              (editscript/patch html-1 diff))))))
=> #'ssr/render
(render [:div "b"])
=> "<div>b</div>"
(println (type @current-state))
clojure.lang.APersistentVector$SubVector
=> nil
(render [:div "c"])
Execution error (IllegalArgumentException) at editscript.diff.a-star/associative-children (a_star.cljc:86).
No implementation of method: :kv-reduce of protocol: #'clojure.core.protocols/IKVReduce found for class: clojure.lang.APersistentVector$SubVector

test namespaces need update

lein test doesn't work. The files in test/editscript/diff/* need to have "diff" in their ns declarations. I guess the files were moved without updating the namespaces.

-(ns editscript.a-star-test
+(ns editscript.diff.a-star-test

-(ns editscript.quick-test
+(ns editscript.diff.quick-test

Improve handling of sets

Right now, the sets are handled as maps, i.e. a map of keys to themselves. This will induce non-optimal edits, where a compound set element with slight change will be deleted, then changed element inserted. The correct approach is to try to drill down.

Unresolved reference to goog.math.Long/getMaxValue in newer cljs

[Figwheel:WARNING] Compile Warning   resources/public/js/compiled/editscript/diff/a_star.cljc   line:242  column:24

  Use of undeclared Var goog.math.Long/getMaxValue

  237    ((juxt get-came get-open get-g) state))
  238
  239  (defn- access-g
  240    [g cur]
  241    (get g cur #?(:clj Long/MAX_VALUE
  242                  :cljs (goog.math.Long/getMaxValue))))
                              ^---
  243
  244  (defn ^:declared diff* [ra rb came])
  245
  246  (defn- compute-cost
  247    [^Coord cur came g op]

if you're using the latest releases goog.math.Long/... isn't a valid pattern anymore because that namespace became a goog.module. basically you can longer assume it exists (because some other namespace might have loaded it), you must require it and use an alias

Extending IType

As strange as this may sound, I would like to be able to produce a diff between any two ranges, i.e. as generated by calls to (range ,,,). To give an idea of context, I am using them to produce sequences of DOM nodes which react to changes to an underlying value, which can be anything including a range. The problem is that ranges produce a value of type Range, which editscript treats as being of type :val when calling IType.get-type.

The upshot of this is that when I do perform a diff between two ranges, the resulting script contains a replacement of the entire sequence, e.g.

(e/diff (range 5) (range 10) {:algo :quick})
;;= [[[] :r (0 1 2 3 4 5 6 7 8 9)]]

even when specifying :algo :quick. Obviously for DOM nodes I don't want to replace everything.

My question is, in my own code, can I simply call the following without any repercussions?

(extend-protocol editscript.edit/IType 
   Range 
   (get-type [_] :lst))

The above certainly works, but I was wondering is there a more elegant solution?

`:m` move operator

Hi!

In the context of a tree diff, given an element that is re-parented to a different node, version 0.6.3 produces an editscript that transcribes 2 operations, an addition and a removal:

(e/diff
  [{:a 1} {:b 2} {:c 3}
   [{:m 4} {:z 999} {:n 5}]]

; {:z 999} is re-parented to top of tree
  [{:a 1} {:b 2} {:z 999} {:c 3}
   [{:m 4} {:n 5}]])

;;==> [[[2] :+ {:z 999}] [[4 1] :-]]

Given that {:z 999} has = equality, conceptually an editscript could be produced that transcribes a single move operation, perhaps patterned as:

[ [source-path] :m [dest-path] ]

; Continuing the example above:
[[4 1] :m [2]]

This is valuable in the use case where a tree of data elements is persisted in an external datastore where elements are assigned new IDs on creation. By adhering to an editscript that :- deletes an existing element (thus deleting it from the persistence store, forever blapping that ID) only to :+ re-add the equivalent data in a different location, the element is inadvertently is assigned a new ID by the datastore. Such elements are notionally equivalent but are logically now different on account of the persistence layer ID being changed, and that ID is what other things are keyed on. This gives rise to chaos... ;)

BTW, I'm currently thinking to modify the emitted editscript to identify and collapse such pairs, as a kind of post-processor, but I haven't thought-through the implications for element ordering.

Clojurescript patch issue

Just to check, is this lib supposed to work with clojurescript?

I've been tinkering with records and have managed to find an error if you diff/patch within a record.
The diff is:

(edit/diff (map->TEST {:a 1 :b (map->TEST {:c 2})}) (map->TEST {:a 1 :b (map->TEST {:c 3})}))

Clojure:

(require '[editscript.core :as edit]
         '[editscript.edit :refer [edits->script]]
         '[clojure.edn :as edn])
(defrecord TEST [])

(let [last-state (edn/read-string {:readers {'example_ns.example_test.TEST map->TEST}} "#example_ns.example_test.TEST{:a 1, :b #example_ns.example_test.TEST{:c 2}}")
      updates [{:diff [[[:b :c] :r 3]]}]]
  (reduce (fn [prior {:keys [diff] :as edit}]
            (edit/patch prior (edits->script diff)))
          last-state
          updates))
#example_ns.example_test.TEST{:a 1, :b #example_ns.example_test.TEST{:c 3}}

Clojurescript:

(require '[editscript.core :as edit]
         '[editscript.edit :refer [edits->script]]
         '[clojure.edn :as edn])

(defrecord TEST [])

(let [last-state (edn/read-string {:readers {'example_ns.example_test.TEST map->TEST}} "#example_ns.example_test.TEST{:a 1, :b #example_ns.example_test.TEST{:c 2}}")
      updates [{:diff [[[:b :c] :r 3]]}]]
  (reduce (fn [prior {:keys [diff] :as edit}]
            (edit/patch prior (edits->script diff)))
          last-state
          updates))

#object[Error Error: No matching clause: val]

I could be doing something incorrectly, but not sure what.
EDIT:

(let [last-state (edn/read-string {:readers {'example_ns.example_test.TEST map->TEST}} "#example_ns.example_test.TEST{:a 1, :b 2}")
      updates [{:diff [[[:b] :r 3]]}]]
  (reduce (fn [prior {:keys [diff] :as edit}]
            (edit/patch prior (edits->script diff)))
          last-state
          updates))
#object[Error Error: No matching clause: val]

Even non-nested doesn't work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.