Fork me on GitHub
#specter
<
2017-12-22
>
npetryk02:12:30

Can someone help me understand why (setval (filterer even?) [:example] (range 0 10)) => (:example 1 3 5 7 9) whereas (setval (filterer even?) [] (range 0 10)) => (nil 1 3 5 7 9), yet (setval (filterer even?) [NONE] (range 0 10)) => (1 3 5 7 9)

npetryk02:12:16

being able to resize a subselection is really beautiful, but this special case of resizing it to zero seems a bit odd, and I dont quite understand why it happens

npetryk02:12:16

I would expect replacing a sequence with an empty sequence would remove all elements, rather than leaving a nil in the first matching spot

nathanmarz03:12:45

try (setval (filterer even?) nil (range 0 10))

plamen14:12:01

Hello, I again have a nested map in the form:

plamen14:12:18

{... ...
 ’A {:type :double}
 :variables {... ...
             ’A {:type :int}
			 ’C {:type :double}
             :variables {... ...
			             ’B {:type :double
						     :formula “A + C”}
						 ... ...
			             }
		     ... ...}}

I also recursively traverse it and apply transformations to variables and other nodes (the map is an AST of a DSL)
						 
(defn process-var-list [ast] (transform [NODES-VARIABLES :variables MAP-VALS] (fn [var] ; do something to var ) ast)) `(def NODES-VARIABLES (recursive-path [] p (cond-path (must :variables) (continue-then-stay :variables MAP-VALS map? p))))

plamen14:12:12

In ‘process-vars’ I have access only to the current node which works for most transformations I need. Now if in that DSL I would like to provide variable scope/visibility rules like in Java for example, where the localness of a variable is directly mapped to the depth of it’s definition over the :variables subbranches, I would need to have access not only to var, but to the root of the datastructure AFTER it has been processed in a previous step. That means I can’t just pass ast as a second parameter, because it would have the original state only before any transformation through Specter. collect I think would also not help me in that situation.

plamen14:12:19

So 1. how one can pass not only the currently selected path element, but the whole structure as well in the already modified state on each invocation of the transform function? 2. if I would like that in the context of where variable B is definied, I would like to have access to variable A in the ast, then I should be able to get to the upper and recursively to all further upper levels up to the root from the current node transformed to access the variables defined there. I know how to do it in Clojure without specter, but how with Specter? (for example to pull the types of variables A and C for calculating B) 3. outside of Specter I have a lot of variable and other dependency analysis (essentially expressions/filters over topological sorts of the variables), but is there an idiomatic way to do it in Specter? Any hints would be very helpful

nathanmarz15:12:35

@plamen don't understand what you're asking about getting root of data structure after it's been processed

nathanmarz15:12:22

it would help if you simplified your examples to get at the heart of your questions

plamen15:12:18

Hello Nathan, what I mean is that in the transform function I have as parameter only the current node in the tree as it gets recursed. I would like to pass in addition to it the whole structure so that I can walk it inside the transform function, but not in it’s inital state, but in it’s current (probably already processed state from previous steps in the recursion - my question is probably related to not knowing when exactly the transformation function is applied to the nodes - does first a walk happen in which paths get constructed, then the list is processed, or is it just as in the recursion - step by step depth first, or post-walk or…?) I hope this makes more sense.

plamen15:12:22

(not sure how to make the question simpler as the map with nested :variables keys consisting of lists of other such maps is the simplest one I can think of related to it. This is why I make the analogy with an abstract syntax tree/depth in the tree and variable scope/visibility as in languages like Java/C#)

plamen15:12:08

(it is actually not an AST, but more of a types tree)

plamen15:12:24

so, in the (defn process-var-list [ast] (transform [NODES-VARIABLES :variables MAP-VALS] (fn [var] ; do something to var ) ast))

plamen15:12:42

if I rewrite it to

plamen15:12:44

(defn process-var [var] ;do something and return the transformed var node) (defn process-var-list [ast] (transform [NODES-VARIABLES :variables MAP-VALS] process-var ast))

plamen15:12:04

what I mean is that in my understanding to achieve what I ask for in 1., passing ’ast as an additional argument to ’process-var would pass the inital state of ’ast before the recursion and possible transformation inside it happened?

plamen15:12:41

what I need is that during the recursion, during the invocation from Specter of the function ’process-var, inside ’process-var I get as parameter the ’ast as processed up to there. (which is possible only in pre-walk, but not post-walk, but this is also the point - I don’t know how to control or know when the invocation of ’process-var actually happens)

plamen15:12:45

(sorry - the “possibility” in the “pre-/post-walk” part of the last sentence is wrong)

plamen15:12:47

Does that make more sense?

plamen15:12:13

or in other words which is probably more visual - how to transform

plamen15:12:19

{... ... ’A {:type :double} :variables {... ... ’A {:type :int} ’C {:type :double} :variables {... ... ’B {:type :double :formula “A + C”} ... ... } ... ...}}

plamen15:12:30

{... ... ’A {:type :double} ’D {:type :string} :variables {... ... ’A {:type :int} ’C {:type :double} :variables {... ... ’B {:type :double :uses [[:variables ’A] [:variables ’B]] :sees [’D [:variables ’A] [:variables ’B]] :formula “A + C”} ... ... } ... ...}}

plamen15:12:41

where the vectors in :uses and :sees are either a path in a map as in ’get-in or may be even a Specter path if that’s possible easy.

plamen15:12:14

(this is more about 2. and 3.)

plamen15:12:34

about question 1. another example

nathanmarz15:12:58

so when you process var 1 and then var 2, you want the var 2 transform fn to get the updated ast from processing var 1?

plamen15:12:45

initial tree

plamen15:12:47

{... ... ’A {:type :double} :variables {... ... ’A {} ’C {} :variables {... ... ’B {:type :double :formula “A + C”} ... ... } ... ...}}

plamen15:12:05

when transforming ’A I can add {:type :int} to it

nathanmarz15:12:35

I do exactly that with my graph navigators

plamen15:12:46

and in the next recursion step when I process ’B, the :type values of ’A and ’C are already visible to the invocation of the transform function

nathanmarz15:12:59

my navigators don't navigate directly to a node, but to a pair of [graph node-id]

plamen15:12:11

I know how to do it outside of Specter, but specter gives me much more concise way

nathanmarz15:12:19

so when I do TOPSORT, it navigates to [graph node-id] in topological order, with each step having the updates form previous steps

nathanmarz15:12:39

I believe you can do something similar for your use case with zippers

nathanmarz15:12:48

look at com.rpl.specter.zipper

plamen15:12:53

in my normal clj code I also on each step have something like [root-node local-path-in-the-recursion]

plamen15:12:15

I even did’t tought about zippers, as in normal Clj I achieve it without them. Thank you for the big hint!

nathanmarz15:12:37

zippers are an advanced form of navigation

plamen15:12:47

so when I do TOPSORT, it navigates to [graph node-id] in topological order, with each step having the updates form previous step - was exactly what I was looking it

nathanmarz15:12:55

more indirect but can do things you can't with regular navigators

nathanmarz15:12:05

at the cost of increased overhead

plamen15:12:42

yes - the overhead is not that important for me as it will be used in a compilation phase, where not the compilation, but the runtime is important

plamen15:12:53

while the topological order is important for me, because without it - no chance for correct code generation. but needed to ask first about step 1 and then to think how to overlay the order on top of it in Specter.

plamen16:12:54

reading the doc it looks to me that I have to solve couple of things (and rewire the brain a bit for using zippers)

plamen16:12:39

1. I need to create a specter/zip constructor for nested maps and vectors which can be used from Specter

plamen16:12:16

2. not sure if I read it correctly from the examples, but all S/transform invoke a function with the node itself, but not with the zipper, so how would I navigate back to an upper level if the transform function (in the examples - inc) receives only the current value?

plamen16:12:04

what I mean - from the examples the line (S/transform [SZ/VECTOR-ZIP SZ/DOWN SZ/NODE] inc data) invokes ’inc only with the value of the current node, but how would one consult upper nodes from inside ’inc?

plamen16:12:59

3. not sure yet how to do the recursion as the zip paths are to specific ‘coordinates’. I could using normal clojure recursion where on each node I construct a new zipper and walk/transform from there. Is this the right line of tought?

nathanmarz16:12:23

yea, on second thought zippers won't be enough for what you want to do

nathanmarz16:12:12

you need a custom navigator that understands the data structure as a whole

nathanmarz16:12:18

to do something like the TOPSORT I described

nathanmarz16:12:52

for reference, my TOPSORT code looks like this:

(defn- multi-anchor-path-select [graph getter next-fn]
  (doseqres NONE [anchor (getter graph)]
    (next-fn [graph anchor])))


(defn- multi-anchor-path-transform [graph getter next-fn]
  (reduce (fn [graph node-id]
            (-> [graph node-id] next-fn first))
          graph
          (getter graph)))

(defnav multi-anchor-path [getter]
  (select* [this graph next-fn]
    (multi-anchor-path-select graph getter next-fn))
  (transform* [this graph next-fn]
    (multi-anchor-path-transform graph getter next-fn)))

(def TOPSORT (multi-anchor-path topsort))

plamen18:12:05

Thank you for the direction, Nathan! I tought it is something trivial 🙂 Now I need to rethink

plamen18:12:19

And for taking time again for my questions!