Fork me on GitHub
#specter
<
2016-09-21
>
puzzler01:09:33

@nathanmarz I can see the value, but as far as I can tell this wouldn't help with the precisely symmetric scenario of wanting to disj a single item from a set nested in a data structure .

puzzler01:09:13

@nathanmarz In general, I've been thinking about how assoc-in and setval automatically add levels to your data structure if needed, but I am not aware of a convenient way to make the path down to a leaf go away when the leaf becomes empty.

puzzler02:09:44

I'm having trouble building up a good mental model of how the complex navigators work. For example, I don't understand why these two navigators behave so differently with setval:

puzzler02:09:47

Sorry, that wasn't quite the right example

puzzler02:09:55

=> (setval [(filterer number?)] [:a :b :c] [:x 2 :y 4 :z 6])
[:x :a :y :b :z :c]
=> (setval [(walker number?)] [:a :b :c] [:x 2 :y 4 :z 6])
[:x [:a :b :c] :y [:a :b :c] :z [:a :b :c]]

puzzler02:09:00

There we go.

nathanmarz02:09:26

@puzzler I think things like removing from a sequence or a map are fairly easy with variants on ALL or keypath https://github.com/nathanmarz/specter/issues/117

nathanmarz02:09:58

the trickier case is removing an entire path, such as specifying to remove a nested map when it becomes empty

nathanmarz02:09:30

I've done things like making navigators such as non-empty-keypath, but I'm not sure having so many variants is the right approach

nathanmarz02:09:03

filterer navigates to a single value, while walker navigates to many values

nathanmarz02:09:40

think of filterer as working just like filter, sequence -> single sequence, except it also propagates changes back

nathanmarz02:09:53

whereas walker is like ALL

puzzler02:09:51

Is there a way to predict this single value vs. multi value behavior from the info on the doc page?

nathanmarz02:09:18

(setval (subselect (walker number?)) [:a :b :c] [:x 2 :y 4 :z 6]) ;; => [:x :a :y :b :z :c]

puzzler02:09:31

Similarly, I was confused the first time I encountered that setval on END expected a sequence, not a single value.

nathanmarz02:09:35

filterer uses subselect and ALL

nathanmarz02:09:53

the doc for filterer could be clearer, but I think the doc for END is pretty clear

nathanmarz02:09:08

"Navigate to the empty subsequence after the last element of the collection."

puzzler02:09:33

Yes, END was clear once I looked it up. Just didn't match my intuition, I guess. If these things are just something you have to learn over time, I guess that's the nature of complex libs, but I'm trying to figure out if there's some big-picture way of thinking about it that makes it clear without looking these details up. Sort of like how, in Clojure, once you realize that "sequence functions" put the sequence as the last input, and "collection functions" put the collection as the first input, it's a lot easier to remember.

nathanmarz02:09:44

the only way to do that would be with a naming convention, but that could get unwieldy

nathanmarz02:09:39

like you might want to include in the name whether it navigates to many values or a single value, to a substructure, subvalue, or view, etc

puzzler02:09:04

So that list you just typed is very interesting to me, because I realize I don't have a clear mental model of what kinds of things you can navigate to, when you talk about substructure, subvalue, view, etc. and what the different behavior of setval, transform, and select would be under those scenarios.

nathanmarz02:09:53

substructure is something like srange or subset, where it's the same structure as the parent but with a subset of the values

nathanmarz02:09:00

transforms on substructures transform the parent

nathanmarz02:09:23

subvalues are the common case where you literally navigate to that value inside the structure

nathanmarz02:09:57

and views navigate you to something with no structural relation to the input

nathanmarz02:09:30

something like view totally replaces it's input

nathanmarz02:09:32

on transform

nathanmarz02:09:39

I don't think these are hard boundaries between navigators, subselect is like a combination of substructure and view

puzzler02:09:36

Interesting. That's helpful to know. Thanks.

nathanmarz03:09:03

learning to think in terms of substructure is probably one of the major milestones in using specter to its potential

puzzler03:09:00

So the other topic I mentioned is something I encounter a fair amount in my programming. Let's say I want to keep a map that bins words by first letter. So I start out with {}. Now, I add the word apple {\a #{"apple"}} and then banana and ape {\a #{"apple" "ape"} \b #{"banana"}}. Now I remove apple. {\a #{"ape"} \b #{"banana"}} Now I remove ape and I want it to go to {\b #{"banana"}}. A similar scenario is a map of numeric counters. If a counter doesn't yet exist in the map, I create it at 0 and increment it to 1 on demand. When it decrements back to 0, I'd like the counter to disappear from the map.

nathanmarz03:09:16

I've thought about that a bit, and I think ideally this kind of information would be encoded and handled within the data structure implementation

nathanmarz03:09:06

you could do it with navigators like this: (setval [(non-nil-keypath \a) (nil<->val #{}) (subset #{"apple"})] #{} data), but I think it would work better at the data structure impl level

nathanmarz03:09:31

so for example a map would understand what "empty" values look like, and would automatically remove them if they become empty

nathanmarz03:09:51

and it would know how to initialize a non-existent key to the empty value

nathanmarz03:09:51

then you could do with specter: (setval [(keypath \a) (subset #{"apple"})] #{} data) and everything would work in a nice and elegant manner

nathanmarz03:09:08

and it composes nicely to arbitrarily complex data structure combinations

nathanmarz03:09:47

constructing a data structure like that would be something like (def my-map ((hash-map (hash-map (hash-set empty-nil))))

puzzler05:09:47

Are non-nil-keypath and nil<->val new? I don't see them in latest stable or in the github repo.

puzzler05:09:01

A bit of a testimonial: Prior to now, I've been aware of specter, but figured I'd reach for it only if I really needed it -- get-in and update-in are usually sufficient for me. Today, I decided to use specter for the "trivial" things I could do with get-in and update-in; once I had it at my disposal, within a few hours I started seeing several non-trivial uses for it as well which made my implementation a lot cleaner.

puzzler05:09:08

Was everything in specter motivated by actual use-cases? There are several things I can't figure out what the utility would be, and I'm wondering whether some of the things are there just because they were an experiment to see if something was doable, or whether each one actually solves some particular problem that comes up in practice.

nathanmarz11:09:28

@puzzler no those are hypothetical navigators... non-nil-keypath would remove the value if it becomes nil and nil<->val would navigate to val if it's nil and transform to nil if it's val. They would be trivial to implement

nathanmarz11:09:11

@puzzler yes, I use most of what comes with Specter, plus I have a whole lot of private navigators for dealing with DAGs

nathanmarz11:09:07

I think the only ones I don't use are ATOM and the zipper navigators

nathanmarz11:09:05

for which ones do you have trouble seeing the utility?

jvuillermet20:09:42

Is this how I'm supposed to require specter macros ?

jvuillermet20:09:46

(:require-macros [com.rpl.specter.macros :refer [transform]]

jvuillermet20:09:50

from clojurescript

jvuillermet20:09:58

Ok just saw the changelog, no more macros namespace

puzzler23:09:56

Haven't imagined a use yet for anything relating to value collection (DISPENSE, collect, collect-one, terminal, terminal-val). Also don't see the point of the "view functions", i.e., transformed and view. I'm also not sure whether STAY and STOP have any direct utility, or if they are only useful in building other navigators. If you're using all these things, then there must be a whole level to specter that I haven't grokked yet, because I couldn't extrapolate from the toy examples on the wiki page to a real-world use case.