This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-09-02
Channels
- # bangalore-clj (4)
- # beginners (30)
- # boot (11)
- # cljsrn (11)
- # clojure (18)
- # clojure-conj (2)
- # clojure-italy (1)
- # clojure-uk (6)
- # clojurescript (14)
- # clojurewerkz (1)
- # core-async (3)
- # fulcro (14)
- # hoplon (12)
- # lumo (7)
- # off-topic (24)
- # onyx (2)
- # portkey (1)
- # protorepl (1)
- # re-frame (8)
- # spacemacs (14)
- # specter (95)
- # unrepl (14)
Quick question: is there a set of “laws” that navigators are expected to follow? For example, I assume I should expect the following:
(= (select path structure) (transform path (fn [x] x) structure))
or
(= 42 (select path (transform path (fn [_] 42) structure)))
etc@hmaurer well, those are both not true
:a
for first one, ALL
for second one shows falsehood
@nathanmarz ha! how so? transforming a path with the identity shouldn’t leave the values under that path unchanged?
(transform (view inc) identity 1)
returns 2
there's not any mathematical relations I can think of between select
and transform
only important property is that select
and transform
traverse same values in same order
that's necessary for subselect
@nathanmarz in the case of (view inc)
, the invariant
(= (select path structure) (select path (transform path (fn [x] x) structure)))
holds as far as I can seeyea, those ones are different
just view
and transformed
@nathanmarz what is your mental model for navigation if the model “navigating to a nested structure” is too limiting/unsuitable?
well most of the time that is my mental model
including for substructure navigators like srange
I literally visualize moving from one portion of a data structure to another
it gets easier with practice
eventually it's like anything else in programming and its just instinctive
@nathanmarz I am having a little bit of difficulty picturing the view
example you mentioned about. When selected (reading) values, this makes sense. We are “viewing” the result of applying a function to every navigated value
However the transform
case seems a bit odd to me. I would expect transform
to apply the inverse function of the function passed to view
when re-constructing the data
(regardless of the fact that, afaik, there is no way to compute the inverse of an arbitrary function in clojure)
@hmaurer that's what parser
does https://github.com/nathanmarz/specter/wiki/List-of-Navigators#parser
think of view
as like a transform function that then continues navigating into the result of the transform
@nathanmarz that clarifies things. So if my mental model looks like this:
[ 1 ] -- inc --> [ 2 ]
and I want to apply a transform to the value in the second box, which I then expect to be “flowed back” to the first box by inverting the computation, then parser
is the way to gothat's right
the only use case we could think of for the function+inverse was parsing, but it could be used for other things as well
view
would stay at the second box
(transform [(view inc) STOP] dec 1) ;; => 2
Wait, I would be navigated to the second box with view
? How so? If I am at the first box and navigate to the second box by applying some computation (here view
), and I then use setval
to set the value on that path (the second box), shouldn’t I expect the computation to be inverted to determine the value of the first box?
If view
navigates to the second box, I don’t get why/how its behaviour differs from parser
in the transform
case
well it stays there
maybe "boxes" is not the right analogy
you can think of view
not navigating at all, just changing the box it's at
and then the rest of the path continues with the new value
in that example I just showed you can see the dec
function is never reached
but the view
persists in the result
it's equivalent to (transform STOP dec (transform STAY inc 1))
sure thing
ah, last but not least, do you have an example of a scenario where view
is useful but parser
would be unsuitable?
@hmaurer mostly I use it in select
s, where its more concise than parser
plus there's no unparse-fn
for those cases
one spot where I use it in a transform
is needing to manipulate a string as a vector of characters
so I do a (view vec)
In that case though parser
‘s behaviour would make sense, since when manipulating a string as a list of characters you are essentially navigating into the substructure of that string, right?
it's actually for converting a vector of strings to a matrix of characters with some default rendering info attached to each character
yea you could have an unparse-fn
for that kind of navigation, but in this case the goal is to have vector of characters in the output
(def card-back
[
".------."
"|//////|"
"|//////|"
"|//////|"
"|//////|"
"`------'"
])
basically that's how I specify "images" and the subsequent transform
converts it to something that can be rendered
(def cards
(into {}
(for [v [2 3 4 5 6 7 8 9 :T :J :Q :K :A]]
[v
(into {}
(for [[s template] {:clubs clubs-template :diamonds diamonds-template
:spades spades-template :hearts hearts-template}]
[s (transform ALL #(apply str %)
(setval [ALL (view vec) ALL (pred= \?)] (last (str v)) template))]
))])))
oh actually in this case it turns templates into vector of strings for each card value
forgot how this code works :/
in this case parser
would work
Mmh. Out of curiosity, is view
the only (shipped with Specter) infraction to the invariant
(= (select path structure) (select path (transform path identity structure)))
?When thinking of navigation as “diving into structure” it seems that this invariant should hold; I am a bit bothered that view
does not maintain it with transform
also transformed
, nil->val
here's a better example of view
transform use case: (transform [(view vec) ALL] (ichar-with-attrs prompt-attrs) prompt)
a "prompt" string is converted to a vector of characters with each character wrapped in a map also containing rendering attributes like color, font, etc.
could also do (transform ALL (ichar-with-attrs prompt-attrs) (vec prompt))
, but the style with view
would be useful if needed to do that transformation if had something like a map of prompts
invariants like that are important for monads where it really does affect the utility of the monad
other than the traversal order thing I mentioned, invariants like that aren't needed for navigation
I was hoping to use such invariants to better understand what navigators can and can’t do
you could try reading the source code of navigators in specter
view
is one of the simplest
navigators are just functions, so they have all the power a function has
@nathanmarz of course, but I assume that if I want other people to be able to use the navigators I write they should follow some expected behaviours
so as not to force them to completely swap their mental model around on every new navigator they encounter
the only types of navigators I've encountered are "subvalue" (e.g. keypath, nthpath, META, ALL), "substructure" (e.g. srange, submap, subset), "filter" (e.g. pred, selected?), and "view" (e.g. view, nil->val, transformed)
index-nav
is an interesting example of a "subvalue" navigator because it navigates you to an implicit value
@nathanmarz oh that’s interesting indeed. The doc on index-nav
is missing from the wiki by the way
@hmaurer yea the wiki is not up to date
the API docs are the most up to date, but the wiki has more examples
richnav
exposes the full navigator interface when defining navigators
https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/protocols.cljc#L3
many navigators use defrichnav
purely as an optimization
defnav
wraps next-fn
and puts vals
in closure to pass along when it's called
with defrichnav
you can avoid all that allocation/indirection by passing the vals
yourself
unfortunately clojure doesn't optimize this case with inlining and neither does the JVM
although in theory a compiler could do it
so I do it manually for some cases where that optimization makes a difference – basically I try to do it wherever the navigation itself is very cheap so extra overhead will have noticeable performance impact
btw vals
in that interface refers to "collected vals" collected with VAL
, collect
, and collect-one