This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-01-22
Channels
- # asami (31)
- # babashka (1)
- # beginners (29)
- # biff (32)
- # cider (6)
- # clojure (29)
- # clojure-europe (7)
- # clojurescript (16)
- # community-development (18)
- # core-typed (11)
- # emacs (8)
- # hyperfiddle (10)
- # lsp (26)
- # nbb (8)
- # off-topic (17)
- # other-languages (1)
- # pedestal (2)
- # reitit (6)
- # releases (1)
- # sci (2)
- # shadow-cljs (19)
Hi:) Suppose I have an arbitrarily nested map:
{
:key1 "1"
:key2 ["arg1", "arg2"]
:key3 {
:key31 nil
:key32 {
:key331 "2"
:key332 nil
}
}
:key4 nil
}
And I wish to extract the submap which contains only the nil
values, that is:
{
:key3 {
:key31 nil
:key32 {
:key332 nil
}
}
:key4 nil
}
What is the recommended (= shortest) way of achieving it? Thanks 🙏What are you trying to achieve?
As in: what is the actual problem you're trying to solve?
I'm given a yaml represented as map with possibly missing values, and I wish to display all the missing values
Is there a difference between {:x nil} and {}?
Do they represent different things?
Also do you know the full schema of the data beforehand or are the keys arbitrary?
Regarding your first question, I'm not sure I follow but empty values are allowed for my use case. And no, I don't have the full schema
Alright. To answer your question you can do that with clojure.walk/postwalk (check out the examples on clojuredocs , if you can't figure it out I'll give you an example once I get to a computer). However, I believe you should reconsider the way your data is modeled if you have control over it. If you need to distinguish between "missing" values and "empty" values it might be a good idea to use different values. (Check out nil punning for why this could be a problem later on)
Cool, I"ll take a look at clojure.walk/postwalk
, haven't used it before. Thanks:pray:
> 1. a yaml represented as map with possibly missing values
> 2. I wish to display all the missing values
> 3. no, I don’t have the full schema
There is a certain contradiction here, that @UEQPKG7HQ may have been alluding to.
You could postwalk the nested structure and check for any nil?
values. But, there are several issues:
1. Are you only checking for nil?
What about empty strings or other invalid values?
2. When you find the nil, you probably want to print the path it was found in (i.e. not just key332
was nil, but that [:key3 :key32 :key332]
was nil. This is doable, but this is going to require more work - clojure.walk will not give you this context path for free.
3. What if a key is simply not there? (This is why "find missing values" but "I don't know what keys to expect" feel like a contradiction for me)
My point is: 1. You need to consider these additional cases when implementing your solution 2. Perhaps you should consider using a validation library that solves the above problems for you? e.g. clojure.spec or malli
BTW, whenever I work with clojure.walk and need to keep track of context, the programming feels very imperative to me. If you asked me to write up something that does exactly what you asked for (aside from the questions I previously raised why this may not be ideal), I'd probably come up with something like this:
(defn find-nil-keys [data]
(let [!kvs (atom [])]
(walk/postwalk
(fn [x]
(when (map? x)
(swap! !kvs into (seq x)))
x)
data)
(filter (comp nil? second) @!kvs)))
I don't think this is recommend, shortest, or idiomatic. It feels like programming with duct tape, and I wonder how others would write it.
You don't need an atom, I think this will also work:
(defn find-nil-keys [data]
(let [f (fn [[k v]] (when (or (map? v) (not v)) [k v]))]
(clojure.walk/postwalk
(fn [x] (if (map? x)
(into {} (map f x))
x))
data)))
(def thing {:some/key "lslsls" :x [1 2 3] :foo {:zoo :he :bar {:a nil :b {:d [:e nil] :f nil}}}})
(find-nil-keys thing)
;;=> {:foo {:bar {:a nil, :b {:f nil}}}}
> I don't think this is recommend, shortest, or idiomatic
+1@UEQPKG7HQ your version does not flatten the map, which is how I understood the problem.
My version returns pairs (or we could just map first
since the invalid value is always nil
in this case):
(map first (find-nil-keys data))
;; => (:key332 :key31 :key6 :key4)
Oh, I see, I based my solution on this: > I wish to extract the submap which contains only the nil values but yeah the end goal is probably the paths
Maybe something with tree-seq instead of an atom could work? Never really grokked it 😕
I think if we sit down we could come up with some version we're kind of happy with. But I feel we may be solving for a local maxima and missing the forest for the trees. I think both of our previous comments/questions stand. :)
BUT @U04FGP6EJ20 - if you're interested in transforming one nested structure to a slightly different nested structure (e.g. where you only keep maps with nil values), then @UEQPKG7HQ suggestion is totally valid and my comments were off base: use postwalk 👍
If you want a sequence of (path, value) pairs out of a nested map, you might be able to steal some code from this Clojureverse thread: https://clojureverse.org/t/how-to-transform-nested-map-into-flat-sequence-of-path-value-pairs/8801 Then "give me all nil keys" is just a filter!
Hey all, wanted to confirm my understanding of recur. I am trying to solve a question on Exercism where we have to write a method which simulates what map
function do on a sequence
. So I wrote it two ways (well second one is inspired by one of the solution I peeked at), one using recursion and other with recur keyword.
(defn accumulate [action coll]
(if (empty? coll)
[]
(let [x (first coll)]
(cons (action x)
(accumulate action (rest coll)))))) <-- recur not working here.
(defn accumulate-2 [action coll]
(loop [coll coll, acc []]
(if (empty? coll)
acc
(recur (rest coll) (conj acc (action (first coll)))))))
Now based on my understanding recur
is basically optimized way of doing recursion. However, if I try to switch accumulate
to recur
in first function I am getting Can only recur from tail position
. Initially I thought that this might be because recur
is called from inside cons
so it is not in tail position. However in the second method where it works recur
is inside of if
so that same logic should apply there as well right?The way I think of tail position is “does this function have to do any more work after the call to recur
”?
So for the first version, it has to do the recur
and then the current function has to cons
something to that result.
In the latter function, there’s a check. And if the check fails it does the recur. But the current function has no work to do.
This is because when you recur
you can’t come back: it’s basically a loop and there’s no way to save current state and come back to it.
also remember that if isn't regular function. it is a macro that expands to special form if* which has slightly different execution pattern
> “does this function have to do any more work after the call to recur
”
Ohh this makes so much sense. Thanks
> also remember that if isn’t regular function. it is a macro that expands to special form if* which has slightly different execution pattern
I’ll try to keep this in mind, haven’t reached macro part yet 😞 so don’t know what it is.
the main difference important here - function evaluate it's arguments before the call, macro doesn't.