Fork me on GitHub
#specter
<
2017-08-25
>
sandbags17:08:55

@nathanmarz here's my use case, see if you think Specter makes this easier. I'm building a library for modular behaviour trees. i've decided to represent these as nested vectors a la Hiccup markup so, for example [:selector [:loop {:count 99} [:sequence [:randomly {:p 0.5} [:foo]] [:randomly {:p 0.25} [:bar]] [:baz]]]]

sandbags17:08:59

what i want to do is (1) label each vector with an autogenerated id, (2) check that required options are specified (e.g. :loop must always have a :count option) (3) check required number of children is specified (e.g. :loop always has exactly 1 child)

sandbags17:08:40

i have code doing this based on postwalk (well i've not done 3 yet but it's a trivial modification of 2)

sandbags17:08:06

at the moment I am hoisting the options map (and the id) into the metadata for the vector

sandbags17:08:37

i'm undecided whether a better approach would be to add an options map to all vectors and put the id in there instead

nathanmarz18:08:34

@sandbags yes, this is exactly the kind of stuff specter excels at

nathanmarz18:08:43

i'll show you an example

sandbags18:08:57

(one thing that the walk approach does not seem to make particularly easy is giving diagnostic information)

nathanmarz18:08:27

(def data [:selector [:loop {:count 99} [:sequence [:randomly {:p 0.5} [:foo]] [:randomly {:p 0.25} [:bar]] [:baz]]]])

(def ALL-VECTORS
  (recursive-path [] p
    (if-path vector?
      (continue-then-stay ALL p))))

(let [data2 (transform (subselect ALL-VECTORS META :id) (fn [s] (-> s count range)) data)]
  (select [ALL-VECTORS (collect-one META :id)] data2))
;; =>
[[0 [:foo]]
 [1 [:randomly {:p 0.5} [:foo]]]
 [2 [:bar]]
 [3 [:randomly {:p 0.25} [:bar]]]
 [4 [:baz]]
 [5
  [:sequence
   [:randomly {:p 0.5} [:foo]]
   [:randomly {:p 0.25} [:bar]]
   [:baz]]]
 [6
  [:loop
   {:count 99}
   [:sequence
    [:randomly {:p 0.5} [:foo]]
    [:randomly {:p 0.25} [:bar]]
    [:baz]]]]
 [7
  [:selector
   [:loop
    {:count 99}
    [:sequence
     [:randomly {:p 0.5} [:foo]]
     [:randomly {:p 0.25} [:bar]]
     [:baz]]]]]]

nathanmarz18:08:06

that's adding the auto-generated id to the metadata of each vector, the select call is just so you can see what it did

sandbags18:08:24

interesting, thank you

sandbags18:08:01

i definitely need to go back and read the docs again

nathanmarz18:08:28

here's how you could insert ids at index 1 of each vector:

(transform (subselect ALL-VECTORS (srange 1 1)) (fn [s] (->> s count range (map vector))) data)
;;=> [:selector 7 [:loop 6 {:count 99} [:sequence 5 [:randomly 1 {:p 0.5} [:foo 0]] [:randomly 3 {:p 0.25} [:bar 2]] [:baz 4]]]]

nathanmarz18:08:38

and as one last example, here's how you would manipulate a particular node type in the tree:

(defn node-type [kw] (path (selected? FIRST (pred= kw))))

(transform [ALL-VECTORS (node-type :randomly) (nthpath 1) :p] #(* 2 %) data)
;; => [:selector [:loop {:count 99} [:sequence [:randomly {:p 1.0} [:foo]] [:randomly {:p 0.5} [:bar]] [:baz]]]]

sandbags18:08:29

that's very cute

sandbags18:08:38

it's really a shame Specter isn't better known. I know about it and a number of times i've messed around for hours with walk/zippers etc. trying to do something that would have been easier with a bit of Specter

nathanmarz18:08:57

I consider it essential for immutable programming

nathanmarz18:08:18

I've encountered a surprising amount of resistance to it though

sandbags18:08:21

i guess one downside (but probably just inherent in this kind of complex structure bending) is that those expressions take some real picking apart

sandbags18:08:48

it's always going to make for a bit of a learning hill

nathanmarz18:08:53

I wouldn't consider conciseness a downside, but the opposite

nathanmarz18:08:56

definitely a learning curve

nathanmarz18:08:10

what you're seeing here is the result after me learning this stuff for years

sandbags18:08:15

that's what i mean

nathanmarz18:08:37

it's a different way of thinking

nathanmarz18:08:11

but it brings true simplicity and an incredible amount of power over your data

sandbags18:08:42

i don't doubt it

sandbags18:08:16

what do Rich & co. make of it?

nathanmarz18:08:02

no idea, I'm curious about that myself

nathanmarz18:08:19

they haven't said anything about it to my knowledge

sandbags18:08:38

Stuart Holloway, for example, I should have thought would find this interesting.

nathanmarz18:08:08

I suggested contributing specter to clojure awhile back, but the response from core team was "no" with minimal explanation https://groups.google.com/forum/#!searchin/clojure/contribute$20specter%7Csort:relevance/clojure/qN1UPMVQmaM/uegIimKjCwAJ

nathanmarz18:08:49

also saw this recently which makes me think rich would have serious problems with specter: https://gist.github.com/reborg/dc8b0c96c397a56668905e2767fd697f

nathanmarz18:08:14

since he's philosophically against the same function having different performance characteristics on different datatypes, he won't like how I designed the navigators

nathanmarz18:08:01

nthpath for instance being O(n) on lists but faster on vectors (O(log n) I think)

sandbags19:08:07

Ah well, perhaps for the best anyway