braveandtrue

https://www.braveclojure.com/
moo 2018-08-24T13:30:44.000100Z

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)

moo 2018-08-24T13:34:52.000100Z

There must me some difference between {} and ({})

2018-08-24T13:35:48.000100Z

That's part of it. The first argument to conj is the collection you want to append something to.

2018-08-24T13:36:05.000200Z

and the second is the thing you want to add.

moo 2018-08-24T13:36:33.000100Z

Is {:name "Moo" :glitter-index 5} not a collection?

2018-08-24T13:36:40.000100Z

No, it's a map

2018-08-24T13:37:06.000100Z

There are some contexts where you can treat a map as though it were a coll, but conj isn't one of them.

moo 2018-08-24T13:37:07.000300Z

so many maps is a collection, but a single mapping is not

2018-08-24T13:37:13.000100Z

Right

moo 2018-08-24T13:37:54.000100Z

So, the error persistentArrayMap is telling me I need this [{stuffs}]

moo 2018-08-24T13:37:58.000100Z

?

moo 2018-08-24T13:38:38.000100Z

Great everything makes sense

moo 2018-08-24T13:38:48.000100Z

(into [{:name "Moo" :glitter-index 5}] initial-suspects) works

2018-08-24T13:39:11.000200Z

Yes, that'll work too

moo 2018-08-24T13:39:43.000100Z

conj doesn’t work in the above case because it makes a collection of two collections

2018-08-24T13:39:50.000200Z

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

2018-08-24T13:40:50.000100Z

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.

moo 2018-08-24T13:41:01.000100Z

makes sense

moo 2018-08-24T13:41:23.000100Z

Thank you again @manutter51 ~

moo 2018-08-24T13:41:25.000100Z

!

2018-08-24T13:41:31.000100Z

Anytime

2018-08-24T13:47:21.000100Z

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.)

moo 2018-08-24T13:54:46.000100Z

k. I found the coll? function. 🙂

moo 2018-08-24T13:54:50.000100Z

That’ll help

moo 2018-08-24T13:56:41.000100Z

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]]?

moo 2018-08-24T13:57:31.000100Z

What is the empty map doing in there?

moo 2018-08-24T13:57:40.000100Z

0_o

2018-08-24T13:59:15.000100Z

The empty map is for the reduce function to use as a starting value.

2018-08-24T14:01:17.000100Z

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

2018-08-24T14:17:59.000100Z

Ok, back.

2018-08-24T14:20:46.000100Z

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.

moo 2018-08-24T14:22:31.000100Z

Can you destructure maps by specific keys?

2018-08-24T14:23:05.000100Z

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] ]

moo 2018-08-24T14:23:21.000100Z

I caught that part

2018-08-24T14:23:33.000100Z

Yes, you can destructure maps by specific keys too

2018-08-24T14:23:53.000100Z

Ok, so that’s what’s going on with the function inside the reduce

2018-08-24T14:24:35.000100Z

Did I answer your question about the empty map arg?

moo 2018-08-24T14:24:49.000100Z

like: (fn [my-map-type [name address]] (clojure.string/join ", " [name address]))

moo 2018-08-24T14:25:23.000100Z

off to the repl!

moo 2018-08-24T14:26:53.000100Z

yeah, I don’t know how to pull keys by value in the destructuring

moo 2018-08-24T14:27:18.000100Z

(map :somekey my-map) works, but how does one do that work destructuring

2018-08-24T14:27:33.000100Z

just a sec, just got pulled in on another call

moo 2018-08-24T14:29:15.000100Z

I found this: https://clojure.org/guides/destructuring#_associative_destructuring

moo 2018-08-24T14:32:15.000100Z

(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"]

2018-08-24T14:32:41.000100Z

Yeah, that’s the official reference. What I find easiest is just to do the (let [{:keys [name1 name2]} my-map] ...)

2018-08-24T14:33:31.000100Z

Or for a function it would look like (fn [{:keys [name]}] (str name) ...)

moo 2018-08-24T14:33:59.000200Z

strange that is going to grab the values

2018-08-24T14:34:26.000100Z

Yeah, that is totally an idiom. Or “syntactic sugar” if you prefer

moo 2018-08-24T14:34:39.000100Z

actually, I’m getting the same problem

moo 2018-08-24T14:34:52.000100Z

(fn [{:keys [name]}] (str name) (take 1 initial-suspects))
fwpd.core=>
#object[fwpd.core$eval2165$fn__2167 0x15e29d5c "fwpd.core$eval2165$fn__2167@15e29d5c"]

moo 2018-08-24T14:35:10.000100Z

with

(take 1 initial-suspects)
fwpd.core=>
({:name "Edward Cullen", :glitter-index 10})

2018-08-24T14:35:39.000100Z

You’re close, but you’re putting the (take 1 initial suspects) inside the fn

moo 2018-08-24T14:35:50.000100Z

oh

2018-08-24T14:35:57.000100Z

You want it to be the value you pass to the fn

moo 2018-08-24T14:36:36.000100Z

I see

moo 2018-08-24T14:36:44.000200Z

one sec

moo 2018-08-24T14:37:45.000100Z

((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)

moo 2018-08-24T14:38:27.000100Z

well, I can tell I’m going to get good a paren matching. Haha

2018-08-24T14:39:14.000100Z

heh

moo 2018-08-24T14:40:05.000100Z

((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)

moo 2018-08-24T14:41:22.000100Z

(defn try-it (fn [{:keys [name]}] (println "what will happen now?")))
IllegalArgumentException Parameter declaration "fn" should be a vector 

2018-08-24T14:41:32.000200Z

oh, ok, yeah, (take 1 initial-suspects) is returning a collection of maps, not just a map. Try (first initial-suspects)

moo 2018-08-24T14:41:38.000100Z

so something is wrong with my anonymous function definition

moo 2018-08-24T14:42:38.000100Z

works!

2018-08-24T14:43:19.000100Z

:thumbsup:

moo 2018-08-24T14:54:28.000100Z

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"

moo 2018-08-24T14:54:54.000100Z

yay. Thanks @manutter51!

👍 1
2018-08-24T15:06:32.000100Z

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.

2018-08-24T15:08:33.000100Z

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.

moo 2018-08-24T15:15:19.000100Z

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.

moo 2018-08-24T15:16:01.000100Z

is it that I destructure immediately?

moo 2018-08-24T15:16:18.000100Z

before the fn call?

2018-08-24T15:23:58.000100Z

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.

moo 2018-08-24T15:25:12.000100Z

gotcha

moo 2018-08-24T15:25:18.000100Z

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.

moo 2018-08-24T15:25:45.000100Z

like loop based towers of hanoi is better than recursion

2018-08-24T15:26:17.000100Z

Right, tail-call recursion is ok IF you have the optimization that re-uses the stack frame instead of piling up on the stack.

2018-08-24T15:26:39.000100Z

(which Clojure does by using loop/`recur`)

moo 2018-08-24T15:27:09.000100Z

maybe after ch5 I”ll try to do towers of hanoi with loop recur

moo 2018-08-24T15:27:33.000100Z

memoize is mega-handy!

2018-08-24T15:27:38.000100Z

😄

2018-08-24T15:27:44.000200Z

It is pretty cool

moo 2018-08-24T15:27:57.000100Z

I wonder how long it’s going to take to get to being comfortable to build a react app in re-frame

moo 2018-08-24T15:28:03.000100Z

I’m betting 8 weeks

2018-08-24T15:28:59.000200Z

Possibly, maybe more maybe less. I’m still learning nuances and stuff and I’ve been using it for a good while now

moo 2018-08-24T15:29:11.000200Z

I have a sneaking feeling that clojure is going to wind up being like mathematica

moo 2018-08-24T15:29:18.000100Z

that’s the closest to lisp I’ve ever come

2018-08-24T15:29:55.000100Z

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.

2018-08-24T15:30:34.000100Z

Just the fact that Clojure uses [] and {} in a sensible way, instead of () everywhere, makes a HUGE difference to me

moo 2018-08-24T15:31:01.000100Z

more visual cues as to the context

moo 2018-08-24T15:31:21.000100Z

the other lisps don’t have [] or {}

moo 2018-08-24T15:31:24.000200Z

?

moo 2018-08-24T15:31:28.000100Z

yeah that’d be harder

2018-08-24T15:31:49.000100Z

Not that I’m aware of. Everything is parens.

moo 2018-08-24T15:32:20.000100Z

mathematica has funny search and replace, which you can use on your code if you suspend evaluation. But, … I never wrote anything you’d call a program. It’s more like, simulate something or calculation something

moo 2018-08-24T15:32:35.000100Z

but that sounds like code as data

moo 2018-08-24T15:32:42.000100Z

anyhoo back to it 🙂