This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-08-24
Channels
- # announcements (3)
- # beginners (128)
- # boot (2)
- # braveandtrue (97)
- # calva (13)
- # cider (4)
- # cljdoc (12)
- # cljs-dev (16)
- # clojure (78)
- # clojure-germany (8)
- # clojure-italy (5)
- # clojure-nl (1)
- # clojure-spec (59)
- # clojure-uk (29)
- # clojurescript (46)
- # core-async (9)
- # cursive (63)
- # data-science (3)
- # datomic (22)
- # devcards (1)
- # duct (7)
- # emacs (4)
- # flambo (2)
- # fulcro (37)
- # instaparse (6)
- # jobs-discuss (38)
- # juxt (1)
- # off-topic (35)
- # om-next (1)
- # parinfer (7)
- # re-frame (14)
- # reagent (6)
- # reitit (21)
- # rum (1)
- # shadow-cljs (74)
- # spacemacs (8)
- # specter (1)
- # sql (3)
- # testing (2)
- # unrepl (2)
- # yada (6)
Hi All, I have a question about Ch4 ex 2. - “Write an append
function for suspects”
For some reason one version of my code works and one does not. I don’t understand the difference. Starting from here:
(def initial-suspects (mapify (parse (slurp filename))))
initial-suspects
;; => ({:name "Edward Cullen", :glitter-index 10} {:name "Bella Swan", :glitter-index 0} {:name "Charlie Swan", :glitter-index 0} {:name "Jacob Black", :glitter-index 3} {:name "Carlisle Cullen", :glitter-index 6})
This works:
(conj initial-suspects {:name "Moo" :glitter-index 5})
And this does not:
(conj {:name "Moo" :glitter-index 5} initial-suspects)
;; ==> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to java.base/java.util.Map$Entry clojure.lang.APersistentMap.cons (APersistentMap.java:42)
That's part of it. The first argument to conj
is the collection you want to append something to.
and the second is the thing you want to add.
No, it's a map
There are some contexts where you can treat a map as though it were a coll, but conj
isn't one of them.
Right
Yes, that'll work too
Partly because into
is smarter about pulling one collection into another, but also because you've got square brackets around the map, making it a collection of maps
The reason conj
doesn’t work is because it’s designed to take a list of things, and “conjoin” one more thing onto the list.
Thank you again @manutter51 ~
Anytime
I should mention that there are some circumstances under which you can have a map as the first arg to conj
, but for now it’s probably better to just assume you shouldn’t do that. (It’s a little complicated to explain how and why it can work.)
Another Q, regarding destructuring. What is happening here with new-map
?
(reduce (fn [new-map [key val]]
(assoc new-map key (inc val)))
{}
{:max 30 :min 10})
; => {:max 31, :min 11}
It like this? [some-collection [destructuring-some-collection1… n]]
?The empty map is for the reduce
function to use as a starting value.
I’m just getting pulled in to a meeting here, I’ll be back in 20-30 min and I can go into more detail
Ok, back.
There’s one weird thing that Clojure does with maps that I thought was a little confusing when I was a beginner, and that’s how maps behave with iterators like reduce
and map
and filter
and so on.
Basically, when you iterate over a map, Clojure pretends it’s really an array of 2-element arrays, so like this:
{:max 30 :min 10} ;; ==> [ [:max 30]
;; [:min 10] ]
Yes, you can destructure maps by specific keys too
Ok, so that’s what’s going on with the function inside the reduce
Did I answer your question about the empty map arg?
just a sec, just got pulled in on another call
(fn [{name :name}] (str name) (take 1 initial-suspects))
returns some object reference…
; ==> #object[fwpd.core$eval2099$fn__2101 0x3c47f026 "fwpd.core$eval2099$fn__2101@3c47f026"]
Yeah, that’s the official reference. What I find easiest is just to do the (let [{:keys [name1 name2]} my-map] ...)
Or for a function it would look like (fn [{:keys [name]}] (str name) ...)
Yeah, that is totally an idiom. Or “syntactic sugar” if you prefer
(fn [{:keys [name]}] (str name) (take 1 initial-suspects))
fwpd.core=>
#object[fwpd.core$eval2165$fn__2167 0x15e29d5c "fwpd.core$eval2165$fn__2167@15e29d5c"]
You’re close, but you’re putting the (take 1 initial suspects)
inside the fn
You want it to be the value you pass to the fn
((fn [{:keys [name]}] (str name)) (take 1 initial-suspects))
IllegalArgumentException No value supplied for key: {:name "Edward Cullen", :glitter-index 10} clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
((fn [{:keys [name]}] (println "what will happen now?")) (take 1 initial-suspects))
IllegalArgumentException No value supplied for key: {:name "Edward Cullen", :glitter-index 10} clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
(defn try-it (fn [{:keys [name]}] (println "what will happen now?")))
IllegalArgumentException Parameter declaration "fn" should be a vector
oh, ok, yeah, (take 1 initial-suspects)
is returning a collection of maps, not just a map. Try (first initial-suspects)
:thumbsup:
well I don’t know if this is idomatic, but here is what I ended up with for problem 4.
(defn csv-line [{:keys [name glitter-index]}] (str (clojure.string/join "," [name glitter-index]) "\n"))
(reduce str (map csv-line initial-suspects))
;;==> "Edward Cullen,10\nBella Swan,0\nCharlie Swan,0\nJacob Black,3\nCarlisle Cullen,6\n"
By the way, as a stylistic issue, when I’m writing application code, I prefer to do something like this:
(defn csv-line [m]
(let [{:keys [name glitter-index]} m]
...)
In other words, I don’t do destructuring in function arguments, I take the full map as an argument, and then use let
inside the fn to do the destructuring. The reason I like that better is because if this is a production app, I know that some day I’m going to come back to this function because somewhere I made a change, and this code broke. By accepting the whole map as an argument, I can sneak in a line just before the let
that says (prn m)
, as a debugging step. Is a key missing? Is it present, but somehow set to null? I’ve got the whole map coming in, and I can dump it to the console and see exactly what’s different.There have been times I’ve done this and discovered that somehow this fn was being called with the wrong map entirely! Much easier to discover when you have the whole map to work with instead of just some subset of the values.
In your code above I see that you take in the map m
and destructure in the let
scope. That makes sense. I can see how that “looks better.” I also see how I didn’t take in a general map m
, but I didn’t understand how my code is more brittle.
The destructuring happens in the process of evaluating the arguments to your function (which is getting pretty technical, so you don’t need to worry to much about that). I should also add that it’s not at all uncommon to see professional Clojurists putting the destructuring right in the function args, just like your code does. It’s perfectly legitimate, and my style is purely a personal preference.
As a side note, before looking into FP, I never knew about tail-call recursion being okay. I was taught to avoid recursion and so as not to worry about stack overflow.
Right, tail-call recursion is ok IF you have the optimization that re-uses the stack frame instead of piling up on the stack.
(which Clojure does by using loop
/`recur`)
It is pretty cool
I wonder how long it’s going to take to get to being comfortable to build a react app in re-frame
Possibly, maybe more maybe less. I’m still learning nuances and stuff and I’ve been using it for a good while now
Yeah, I tried multiple times to get comfortable with a Lisp, but Clojure’s the one that made it easy enough that I could actually use it.
Just the fact that Clojure uses []
and {}
in a sensible way, instead of ()
everywhere, makes a HUGE difference to me
Not that I’m aware of. Everything is parens.