The Art of Tree Shaping with Zippers

About me

My name is Arne Brasseur (@plexus)

I’m from Belgium, but based in Berlin.

I do Clojure consulting & training.

Lambda Island

Detailed screencasts about all things Clojure & ClojureScript.

Been going for ~2.5 years.

You should check it out.

Agenda

clojure.zip API

What is a zipper?

A zipper (or loc) combines two pieces of information

zipper = tree + path

What does it do?

The zipper API allows you to

Getting started

(require '[clojure.zip :as z])

(def loc (z/vector-zip [[1 2] [3 4]]))

Note: I’m using loc, location, and zipper interchangeably.

What qualifies as a tree?

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip)
[[[1 2] [3 [4 5] 6]] nil]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down)
[[1 2]
 {:l [],
  :pnodes [[[1 2] [3 [4 5] 6]]],
  :ppath nil,
  :r ([3 [4 5] 6])}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right)
[[3 [4 5] 6]
 {:l [[1 2]],
  :pnodes [[[1 2] [3 [4 5] 6]]],
  :ppath nil,
  :r nil}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down)
[3
 {:l [],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r ([4 5] 6)}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost)
[6
 {:l [3 [4 5]],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r nil}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left)
[[4 5]
 {:l [3],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r (6)}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x))
[[4 5 :x]
 {:l [3],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r (6),
  :changed? true}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x)
    z/up)
[[3 [4 5 :x] 6]
 {:l [[1 2]],
  :pnodes [[[1 2] [3 [4 5] 6]]],
  :ppath nil,
  :r nil,
  :changed? true}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x)
    z/up
    z/node)
;;=> [3 [4 5 :x] 6]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x)
    z/root)
;;=> [[1 2] [3 [4 5 :x] 6]]

xml-zip

<cart>
    <line-items>
        <product sku="CH56" qty="1" price="2.5">Chocolate</product>
        <product sku="CA94" qty="1" price="1.7">Candy</product>
        <discount amount="10%" />
    </line-items>
    <customer>
        <name>Arne</name>
        <address>Berlin</address>
    </customer>
</cart>

xml-zip

(require '[clojure.java.io :as io]
         '[clojure.xml :as xml])

(def cart-xml
  (-> "zipper_demo/cart.xml"
      io/resource
      io/file
      xml/parse))

xml-zip

cart-xml
;;=>
{:tag "cart", :attrs {}
 :content
 [{:tag "line-items", :attrs {}
   :content
   [{:tag "product"
     :attrs {:sku "CH56"
             :qty "1"
             :price "2.5"}
     :content ["Chocolate"]}
    ,,,]}
  {:tag "customer" ,,,}]}

xml-zip

(def cart-zipper
  (z/xml-zip cart-xml))

cart-zipper
[{:tag :cart,
  :attrs nil,
  :content
  [{:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}]}
 nil]

xml-zip

(-> cart-zipper
    z/down)
[{:tag :line-items,
  :attrs nil,
  :content
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}],
  :ppath nil,
  :r
  ({:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]})}]

xml-zip

(-> cart-zipper
    z/down
    z/down)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["Chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right)
[{:tag :product,
  :attrs
  {:price "1.7",
   :qty "1",
   :sku "CA94"},
  :content ["Candy"]}
 {:l
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right
    z/right)
[{:tag :discount,
  :attrs {:amount "10%"},
  :content nil}
 {:l
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r nil}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right
    z/right
    z/leftmost)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["Chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right
    z/right
    z/leftmost
    z/down)
["Chocolate"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :ppath
   {:l [],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r
    ({:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]})},
   :r
   ({:tag :product,
     :attrs
     {:price "1.7",
      :qty "1",
      :sku "CA94"},
     :content ["Candy"]}
    {:tag :discount,
     :attrs {:amount "10%"},
     :content nil})},
  :r nil}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate"))
["luxury chocolate"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :ppath
   {:l [],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r
    ({:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]})},
   :r
   ({:tag :product,
     :attrs
     {:price "1.7",
      :qty "1",
      :sku "CA94"},
     :content ["Candy"]}
    {:tag :discount,
     :attrs {:amount "10%"},
     :content nil})},
  :r nil,
  :changed? true}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate")
    z/up)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["luxury chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}),
  :changed? true}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate")
    z/up
    (z/edit
      assoc-in [:attrs :price] 2.9))
[{:tag :product,
  :attrs
  {:price 2.9,
   :qty "1",
   :sku "CH56"},
  :content ["luxury chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}),
  :changed? true}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate")
    z/up
    (z/edit
      assoc-in [:attrs :price] 2.9)
    z/root)
;;=> {:tag "cart", ...}

API : Creating Zippers

vector-zip
xml-zip
seq-zip
zipper

API: Navigating

up
down
left
right
leftmost
rightmost
root

API: Updating

insert-left
insert-right
append-child ;; rightmost
insert-child ;; leftmost
edit
remove
replace
make-node

API: Inspecting

lefts
rights
children
node
path
branch?

API: Walking

next
prev
end?

Walking

(nth
  (iterate z/next cart-zipper)
  0)
[{:tag :cart,
  :attrs nil,
  :content
  [{:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}]}
 nil]

Walking

(nth
  (iterate z/next cart-zipper)
  1)
[{:tag :line-items,
  :attrs nil,
  :content
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}],
  :ppath nil,
  :r
  ({:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]})}]

Walking

(nth
  (iterate z/next cart-zipper)
  2)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["Chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

Walking

(nth
  (iterate z/next cart-zipper)
  3)
["Chocolate"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :ppath
   {:l [],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r
    ({:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]})},
   :r
   ({:tag :product,
     :attrs
     {:price "1.7",
      :qty "1",
      :sku "CA94"},
     :content ["Candy"]}
    {:tag :discount,
     :attrs {:amount "10%"},
     :content nil})},
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  4)
[{:tag :product,
  :attrs
  {:price "1.7",
   :qty "1",
   :sku "CA94"},
  :content ["Candy"]}
 {:l
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

Walking

(nth
  (iterate z/next cart-zipper)
  5)
["Candy"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}],
  :ppath
  {:l
   [{:tag :product,
     :attrs
     {:price "2.5",
      :qty "1",
      :sku "CH56"},
     :content ["Chocolate"]}],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :ppath
   {:l [],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r
    ({:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]})},
   :r
   ({:tag :discount,
     :attrs {:amount "10%"},
     :content nil})},
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  6)
[{:tag :discount,
  :attrs {:amount "10%"},
  :content nil}
 {:l
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  7)
[{:tag :customer,
  :attrs nil,
  :content
  [{:tag :name,
    :attrs nil,
    :content ["Arne"]}
   {:tag :address,
    :attrs nil,
    :content ["Berlin"]}]}
 {:l
  [{:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}],
  :ppath nil,
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  8)
[{:tag :name,
  :attrs nil,
  :content ["Arne"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}],
  :ppath
  {:l
   [{:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r nil},
  :r
  ({:tag :address,
    :attrs nil,
    :content ["Berlin"]})}]

Walking

(nth
  (iterate z/next cart-zipper)
  9)
["Arne"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}
   {:tag :name,
    :attrs nil,
    :content ["Arne"]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]}],
   :ppath
   {:l
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r nil},
   :r
   ({:tag :address,
     :attrs nil,
     :content ["Berlin"]})},
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  10)
[{:tag :address,
  :attrs nil,
  :content ["Berlin"]}
 {:l
  [{:tag :name,
    :attrs nil,
    :content ["Arne"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}],
  :ppath
  {:l
   [{:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r nil},
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  11)
["Berlin"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}
   {:tag :address,
    :attrs nil,
    :content ["Berlin"]}],
  :ppath
  {:l
   [{:tag :name,
     :attrs nil,
     :content ["Arne"]}],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]}],
   :ppath
   {:l
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r nil},
   :r nil},
  :r nil}]

Walking

(nth
  (iterate z/next cart-zipper)
  12)
[{:tag :cart,
  :attrs nil,
  :content
  [{:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}]}
 :end]

Caveats

Caveats: no boundary checks

(-> [[1 2]]
    z/vector-zip
    z/down ;; [1 2]
    z/down ;; 1
    z/down
    )
;;=> nil
;; ... oops ...

Caveats: no boundary checks

(if-let [l (z/left loc)]
  (do-something-with l)
  (do-something-else loc))

Caveats: no way back from :end

(-> [[1 2]]
    z/vector-zip
    z/next ;; [1 2]
    z/next ;; 1
    z/next ;; 2
    z/next ;; :end
    z/prev)
;; => nil
;; ... oops ...

Caveats: not a cursor

A zipper points at a node, not in between nodes.

Corrolary: a zipper can not enter an empty container.

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down
    z/down
    z/remove
    z/remove
    (z/insert-left 3))
;;=> ???

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip)
[[[1 2]] nil]

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down)
[[1 2]
 {:l [],
  :pnodes [[[1 2]]],
  :ppath nil,
  :r nil}]

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down
    z/down)
[1
 {:l [],
  :pnodes [[[1 2]] [1 2]],
  :ppath
  {:l [],
   :pnodes [[[1 2]]],
   :ppath nil,
   :r nil},
  :r (2)}]

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down
    z/down
    z/remove)
[[2]
 {:l [],
  :pnodes [[[1 2]]],
  :ppath nil,
  :r nil,
  :changed? true}]

Huh?

(defn remove
  "Removes the node at loc, returning the loc that
  would have preceded it in a depth-first walk."
  {:added "1.0"}
  [loc]
  ,,,)

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down
    z/down
    z/remove)
[[2]
 {:l [],
  :pnodes [[[1 2]]],
  :ppath nil,
  :r nil,
  :changed? true}]

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down
    z/down
    z/remove
    z/remove)

Caveats: not a cursor

(-> [[1 2]]
    z/vector-zip
    z/down
    z/down
    z/remove
    z/remove
    (z/insert-left 3))

;; Exception: Insert at top

Caveats: not a cursor

(-> [[:x :y] [1 2]]
    z/vector-zip
    z/down
    z/right)
[[1 2]
 {:l [[:x :y]],
  :pnodes [[[:x :y] [1 2]]],
  :ppath nil,
  :r nil}]

Caveats: not a cursor

(-> [[:x :y] [1 2]]
    z/vector-zip
    z/down
    z/right
    z/remove)
[:y
 {:l [:x],
  :pnodes
  [[[:x :y] [1 2]] [:x :y]],
  :ppath
  {:l [],
   :pnodes [[[:x :y] [1 2]]],
   :ppath nil,
   :r nil,
   :changed? true},
  :r nil}]

Functional Zippers

What is a zipper?

(ns ^{:doc "Functional hierarchical zipper,
            with navigation, editing,
            and enumeration.  See Huet"
      :author "Rich Hickey"}
    clojure.zip)

Gérard Huet

A path is like a zipper, allowing one to rip the tree structure down to a
certain location. It contains its list l of left siblings , its father path p,
and its list r of right siblings.

path = Top | (left siblings, parent path, right siblings)

A location in the tree adresses a subtree, together with its path

location = (current node, path)

xml-zip

(-> cart-zipper)
[{:tag :cart,
  :attrs nil,
  :content
  [{:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}]}
 nil]

xml-zip

(-> cart-zipper
    z/down)
[{:tag :line-items,
  :attrs nil,
  :content
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}],
  :ppath nil,
  :r
  ({:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]})}]

xml-zip

(-> cart-zipper
    z/down
    z/down)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["Chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right)
[{:tag :product,
  :attrs
  {:price "1.7",
   :qty "1",
   :sku "CA94"},
  :content ["Candy"]}
 {:l
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right
    z/right)
[{:tag :discount,
  :attrs {:amount "10%"},
  :content nil}
 {:l
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r nil}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right
    z/right
    z/leftmost)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["Chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil})}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/right
    z/right
    z/leftmost
    z/down)
["Chocolate"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :ppath
   {:l [],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r
    ({:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]})},
   :r
   ({:tag :product,
     :attrs
     {:price "1.7",
      :qty "1",
      :sku "CA94"},
     :content ["Candy"]}
    {:tag :discount,
     :attrs {:amount "10%"},
     :content nil})},
  :r nil}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate"))
["luxury chocolate"
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["Chocolate"]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content ["Berlin"]}]}]}
    {:tag :line-items,
     :attrs nil,
     :content
     [{:tag :product,
       :attrs
       {:price "2.5",
        :qty "1",
        :sku "CH56"},
       :content ["Chocolate"]}
      {:tag :product,
       :attrs
       {:price "1.7",
        :qty "1",
        :sku "CA94"},
       :content ["Candy"]}
      {:tag :discount,
       :attrs {:amount "10%"},
       :content nil}]}],
   :ppath
   {:l [],
    :pnodes
    [{:tag :cart,
      :attrs nil,
      :content
      [{:tag :line-items,
        :attrs nil,
        :content
        [{:tag :product,
          :attrs
          {:price "2.5",
           :qty "1",
           :sku "CH56"},
          :content ["Chocolate"]}
         {:tag :product,
          :attrs
          {:price "1.7",
           :qty "1",
           :sku "CA94"},
          :content ["Candy"]}
         {:tag :discount,
          :attrs {:amount "10%"},
          :content nil}]}
       {:tag :customer,
        :attrs nil,
        :content
        [{:tag :name,
          :attrs nil,
          :content ["Arne"]}
         {:tag :address,
          :attrs nil,
          :content
          ["Berlin"]}]}]}],
    :ppath nil,
    :r
    ({:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]})},
   :r
   ({:tag :product,
     :attrs
     {:price "1.7",
      :qty "1",
      :sku "CA94"},
     :content ["Candy"]}
    {:tag :discount,
     :attrs {:amount "10%"},
     :content nil})},
  :r nil,
  :changed? true}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate")
    z/up)
[{:tag :product,
  :attrs
  {:price "2.5",
   :qty "1",
   :sku "CH56"},
  :content ["luxury chocolate"]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}
   {:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content ["Chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}],
  :ppath
  {:l [],
   :pnodes
   [{:tag :cart,
     :attrs nil,
     :content
     [{:tag :line-items,
       :attrs nil,
       :content
       [{:tag :product,
         :attrs
         {:price "2.5",
          :qty "1",
          :sku "CH56"},
         :content ["Chocolate"]}
        {:tag :product,
         :attrs
         {:price "1.7",
          :qty "1",
          :sku "CA94"},
         :content ["Candy"]}
        {:tag :discount,
         :attrs {:amount "10%"},
         :content nil}]}
      {:tag :customer,
       :attrs nil,
       :content
       [{:tag :name,
         :attrs nil,
         :content ["Arne"]}
        {:tag :address,
         :attrs nil,
         :content
         ["Berlin"]}]}]}],
   :ppath nil,
   :r
   ({:tag :customer,
     :attrs nil,
     :content
     [{:tag :name,
       :attrs nil,
       :content ["Arne"]}
      {:tag :address,
       :attrs nil,
       :content ["Berlin"]}]})},
  :r
  ({:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}),
  :changed? true}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate")
    z/up
    z/up)
[{:tag :line-items,
  :attrs nil,
  :content
  [{:tag :product,
    :attrs
    {:price "2.5",
     :qty "1",
     :sku "CH56"},
    :content ["luxury chocolate"]}
   {:tag :product,
    :attrs
    {:price "1.7",
     :qty "1",
     :sku "CA94"},
    :content ["Candy"]}
   {:tag :discount,
    :attrs {:amount "10%"},
    :content nil}]}
 {:l [],
  :pnodes
  [{:tag :cart,
    :attrs nil,
    :content
    [{:tag :line-items,
      :attrs nil,
      :content
      [{:tag :product,
        :attrs
        {:price "2.5",
         :qty "1",
         :sku "CH56"},
        :content ["Chocolate"]}
       {:tag :product,
        :attrs
        {:price "1.7",
         :qty "1",
         :sku "CA94"},
        :content ["Candy"]}
       {:tag :discount,
        :attrs {:amount "10%"},
        :content nil}]}
     {:tag :customer,
      :attrs nil,
      :content
      [{:tag :name,
        :attrs nil,
        :content ["Arne"]}
       {:tag :address,
        :attrs nil,
        :content ["Berlin"]}]}]}],
  :ppath nil,
  :r
  ({:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}),
  :changed? true}]

xml-zip

(-> cart-zipper
    z/down
    z/down
    z/down
    (z/replace
      "luxury chocolate")
    z/up
    z/up
    z/up)
[{:tag :cart,
  :attrs nil,
  :content
  [{:tag :line-items,
    :attrs nil,
    :content
    [{:tag :product,
      :attrs
      {:price "2.5",
       :qty "1",
       :sku "CH56"},
      :content
      ["luxury chocolate"]}
     {:tag :product,
      :attrs
      {:price "1.7",
       :qty "1",
       :sku "CA94"},
      :content ["Candy"]}
     {:tag :discount,
      :attrs {:amount "10%"},
      :content nil}]}
   {:tag :customer,
    :attrs nil,
    :content
    [{:tag :name,
      :attrs nil,
      :content ["Arne"]}
     {:tag :address,
      :attrs nil,
      :content ["Berlin"]}]}]}
 nil]

Clojure Implementation

clojure.zip/zipper

(zipper branch? children make-node root)
;; Can this node have children?
(branch? node) ;;=> bool
;; Return the children of a branch node.
(children node) ;;=> sequence
;; Given an existing node and a new set of child nodes,
;; return a new node with those children.
(make-node node seq) ;;=> node

clojure.zip/zipper

(defn zipper [branch? children make-node root]
  ^{:zip/branch? branch?
    :zip/children children
    :zip/make-node make-node}
  [root nil])

clojure.zip/zipper

(defn vector-zip
  "Returns a zipper for nested vectors, given a root vector"
  [root]
  (zipper vector?
          seq
          (fn [node children]
            (with-meta (vec children) (meta node)))
          root))

^meta polymorphism

(defn branch?
  [loc]
  ((:zip/branch? (meta loc)) (node loc)))

(defn children
  [loc]
  ((:zip/children (meta loc)) (node loc)))

(defn make-node
  [loc node children]
  ((:zip/make-node (meta loc)) node children))

clojure.zip/zipper

(vector-zip [[1 2] [3 [4 5] 6]])
;;=> [ [[1 2] [3 [4 5] 6]] nil ]
(meta (vector-zip [[1 2] [3 [4 5] 6]]))
;;=>
{:zip/branch? #function[clojure.core/vector?--5314],
 :zip/children #function[clojure.core/seq--5302],
 :zip/make-node #function[clojure.zip/vector-zip/fn--9127]}

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip)
[[[1 2] [3 [4 5] 6]] nil]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down)
[[1 2]
 {:l [],
  :pnodes [[[1 2] [3 [4 5] 6]]],
  :ppath nil,
  :r ([3 [4 5] 6])}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right)
[[3 [4 5] 6]
 {:l [[1 2]],
  :pnodes [[[1 2] [3 [4 5] 6]]],
  :ppath nil,
  :r nil}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down)
[3
 {:l [],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r ([4 5] 6)}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost)
[6
 {:l [3 [4 5]],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r nil}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left)
[[4 5]
 {:l [3],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r (6)}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x))
[[4 5 :x]
 {:l [3],
  :pnodes
  [[[1 2] [3 [4 5] 6]]
   [3 [4 5] 6]],
  :ppath
  {:l [[1 2]],
   :pnodes [[[1 2] [3 [4 5] 6]]],
   :ppath nil,
   :r nil},
  :r (6),
  :changed? true}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x)
    z/up)
[[3 [4 5 :x] 6]
 {:l [[1 2]],
  :pnodes [[[1 2] [3 [4 5] 6]]],
  :ppath nil,
  :r nil,
  :changed? true}]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x)
    z/up
    z/node)
;;=> [3 [4 5 :x] 6]

An example

(-> [[1 2] [3 [4 5] 6]]
    z/vector-zip
    z/down
    z/right
    z/down
    z/rightmost
    z/left
    (z/append-child :x)
    z/root)
;;=> [[1 2] [3 [4 5 :x] 6]]

Fast-zip & rewrite-clj

What’s next?

{fast-zip {:mvn/version "0.7.0"}}
- (:require [clojure.zip :as z])
+ (:require [fast-zip.core :as z])

Identical API, but faster!

Careful not to rely on implementation details.

What’s next?

{rewrite-clj {:mvn/version "0.6.1"}}
(require '[rewrite-clj.zip :as z])

(def loc (z/of-string (slurp "burrito.clj")))

rewrite-clj: base for tooling

Real world example

(defn update-requires [loc renames]
  (loop [loc loc]
    (cond
      (z/end? loc) loc

      (and (z/list? loc) (-> loc z/down z/sexpr (= :require)))
      (recur (-> loc z/down (update-requires* renames) z/next))

      :else
      (recur (z/next loc)))))

THE END

(-> [[[1 2] [3 4]] [[5 6] 7]]
    z/vector-zip
    z/down
    z/down
    z/down
    (z/replace :X)
    z/root)

(assoc-in [[[1 2] [3 4]] [[5 6] 7]]
          [0 0 0]
          :X)