Fork me on GitHub
#clojure
<
2020-09-25
>
jumar12:09:49

I'm reading Chapter 6 from Joy of Clojure and in the section about laziness they claim that next is "less lazy" and should print three dots in the example below - but for me it prints also only two dots. Is that something that has been changed in a more recent version of Clojure?

;; rest vs. next (p. 126)
(def very-lazy (-> (iterate #(do (print \.) (inc %)) 1)
                   rest rest rest))
;;=> prints two dots ..


(def less-lazy (-> (iterate #(do (print \.) (inc %)) 1)
                   next next next))
;;=> prints two dots too?!

jumar12:09:24

Although this works as expected (= stated in the book): Actually, this doesn't work either 😮

(println (first very-lazy))
;;=> prints ".4"
(println (first less-lazy))
;;=> prints ".4" (although it should print just "4")

Alex Miller (Clojure team)12:09:02

iterate has been rewritten since JoC was written

Alex Miller (Clojure team)12:09:11

it's now both seq and self-reducible and written in Java, so all of the details are different

jumar12:09:02

Interesting, thanks!

Alex Miller (Clojure team)13:09:50

the point is still generally valid :)

jumar13:09:08

Yeah, I think it's visible here:

(defn simple-range [i limit]
  (lazy-seq
   ;; nil returned by when will terminate the range construction
   (when (< i limit)
     (print ".")
     (cons i (simple-range (inc i) limit)))))
;; prints 3 dots
(def rrr (rest (rest (rest (simple-range 1 10)))))
;; prints 4 dots
(def nnn (next (next (next (simple-range 1 10)))))

vncz13:09:17

Quick questions on atoms — if I understood the thing correctly the way they should be used is by dereferencing them once at the beginning of where they're used — and not dereference them every single time that is needed, in order to have the same value for the entire time of the computation.

vncz13:09:50

…because dereferencing it every single time that is needed might ultimately give two different values

borkdude13:09:11

that's correct, they're mutable, so they can change from under you.

vncz13:09:45

but if I dereference them once, at the beginning of my computation and save the value using a let binding

vncz13:09:59

I am "protected" — the box can point to something else, the value I dereferenced is immutable

borkdude13:09:21

if you store an immutable value in the atom, yes

vncz13:09:59

Ok perfect, that makes completely sense. Thanks @borkdude

borkdude13:09:33

@vincenz.chianese Note that for updating atoms you generally want to use swap!, if the update function references the current value

borkdude13:09:55

e.g. don't do this:

(def x (atom 1))
(reset! x (inc @x))
but do this instead:
(swap! x inc)

vncz14:09:19

@borkdude Oh yeah for sure, I think I've understood the semantic of swap! and reset!

vncz14:09:21

I was just having some doubts on how the mechanism would make sure that there are no race conditions and weird stuff in multithreads; now that I understood the thing (separate the value from its pointer) it's totally clear.

Marz Drel14:09:12

I am quite new to Clojure and I cannot really grasp why why str/join is ignoring the last argument here. bb '(-> "some str" (#(str/split %1 #" ")) (str/join "-"))'

jsn14:09:39

1. that should be in #beginners 2. that should probably be ->> (`bb '(->> "some str" (#(str/split %1 #" ")) (str/join "-"))'` )

Marz Drel14:09:27

Yeah, thanks.

Marz Drel14:09:48

I understand there are two signatures for clojure.string/join function but I can’t get why the extra argument is ignored here. Using macro for anonymous function of course solves the issue, but shouldn’t this work like this, too. If not - why not?

nwjsmith14:09:30

The -> macro is going to to thread the return values through in the first argument position. The line you have there ends up compiling to: (str/join (#(str/split %1 #" ") "some str") "-") . I think what you'd like is something like this: (str/join "-" (str/split "some str" #" ")).

nwjsmith14:09:22

Does that clear things up?

nwjsmith14:09:25

As you're new to the language, one of the things worth knowing about Clojure is that many of the core functions take a "garbage in, garbage out" approach. So even though your original form evaluates to "-", the call itself has the arguments out-of-order – (str/join ["some" "str"] "-")

👍 3
🙏 3
Marz Drel14:09:45

Yeah, I was sure I was using thread-last macro, many thanks. 🙂

Karol Wójcik14:09:22

Is it possible to use System/getEnv in edn file?

Daniel Stephens15:09:47

not natively as such but we use https://github.com/juxt/aero which adds some extra tags for them

Karol Wójcik15:09:55

Ok great. Do you know whether it's possible to use clj with different deps.edn names? Like deps.development.edn deps.production.edn etc?

vlaaad15:09:37

Aliases are usually used for that...

vlaaad15:09:13

E.g. you have :dev alias with extra paths and deps

Karol Wójcik15:09:12

Perfect! Thank you!

oxalorg (Mitesh)15:09:20

@ad399 When I'm trying to figure stuff out I usually use the macroexpand-all function from clojure.walk to figure out what is the final form. This should be helpful to debug more issues in the future:

(use 'clojure.walk)
(macroexpand-all '(-> "some str" (#(str/split %1 #" ")) (str/join "-"))
This will return
(str/join ((fn* [p1__9737#] (str/split p1__9737# #" ")) "some str") "-")

Marz Drel15:09:51

@mitesh This is very nice, thank you.

👍 3
ivana16:09:37

Are there any suggestions or improvements?

(defn take-sorted-by [c n f]
    (let [m ^TreeMap (TreeMap.)
          m-size (volatile! 0)
          m-add (fn [k v] (if-let [a ^ArrayList (.get m k)]
                            (.add ^ArrayList a v)
                            (.put ^TreeMap m k (doto (ArrayList.) (.add v)))))
          m-sub (fn [k] (let [a ^ArrayList (.get m k)
                              size ^int (.size a)]
                          (.remove a (dec size)) ;; (dec size) have to be int type!
                          (when (.isEmpty ^ArrayList a)
                            (.remove ^TreeMap m k))))]
      (doseq [v c]
        (let [k (f v)]
          (if (< @m-size n)
            (do
              (m-add k v)
              (vswap! m-size inc))
            (let [max-key (.lastKey ^TreeMap m)]
              (when (< k max-key)
                (m-sub max-key)
                (m-add k v))))))
      (->> m vals (apply concat))))

  (defn take-sorted-by-naive [c n f] (->> c (sort-by f) (take n)))

  (let [c (range 100000000)
        n 10
        f #(rem % 15000000)]
    ;; (-> (take-sorted-by-naive c n f) prn time) bad idea, Execution error (OutOfMemoryError)
    (-> (take-sorted-by c n f) prn time))

(0 15000000 30000000 45000000 60000000 75000000 90000000 1 15000001 30000001)
"Elapsed time: 2268.525789 msecs"

emccue21:09:28

c n f v k m

emccue21:09:40

those are all pretty unhelpful variable names

ivana16:09:19

yep, ofc (:import [java.util TreeMap ArrayList])

jjttjj17:09:10

In cases where you need to accumulate a result across a sequence, and check for an end condition after each new item, are there any general guidelines on when to prefer loop/recur vs reduce/reduced? It seems like it might be best to prefer reduce if that is sufficient, since it's higher level. But on the other hand I haven't seen that as often and there might be more nuance involved there?

p-himik17:09:40

I tend to use reduce when there's just one accumulator of sorts. loop for everything else that can't be expressed with other higher-order functions.

6
ivana17:09:06

2 accumulators = one accumulator 🙂

Noah Bogart18:09:32

within a given namespace, is it possible to see which vars aren't defined in it? i mean, which vars are defined elsewhere and required/imported in?

Noah Bogart18:09:58

i have a habit of writing :refer :all, and i want to step through my project's namespaces and convert those :refer :all lines into :refer [specific-var] lines

Noah Bogart18:09:42

and it's a slow process to change it to be (:require ... [other.namespace]) and then compile and see what's failed, and then add them in one at a time

jdhollis17:09:55

One of my favorite books. Nice.

Noah Bogart18:09:21

oh that's cool as heck

Noah Bogart18:09:24

thank you so much

Noah Bogart18:09:29

exactly what i was looking for

borkdude18:09:02

@nbtheduke clj-kondo can also help with this, provided you have linted all your sources so it can infer where vars are coming from in case of refer all

Noah Bogart18:09:13

i should probably hop into #clj-kondo to ask, but it just says "use alias or :refer" for me. is there something specific I should do to make it figure it out for me?

borkdude18:09:35

Like I said, you should also lint the other namespace. Also you should have a .clj-kondo dir if you’re doing it from an editor, so it can save data there

vncz19:09:49

Is there a way to get a collection's item by index and also remove it from the collection, all in one shot?

vncz20:09:56

Essentially, something better than this:

vncz20:09:26

Ideally I would like both

vncz20:09:30

The new collection and the extracted item

schmee20:09:46

vectors aren’t great for that type of thing, depending on what you’re doing maybe you could use an ArrayList instead?

vncz20:09:45

Well essentially I'd like something like const [elem, newVector] = removeElement(originalVector, index);

vncz20:09:56

I'm new to Clojure and my knowledge of Java is literally -1

schmee20:09:18

not a problem! if you can provide some more context I might be able to point you in the right direction

vncz20:09:07

Well again, I'm looking for a built-in function (if any) to do this:

vncz20:09:01

user=> (remove-collection-index [9 8 7 6 5 4 3 2 1 0] 3) [6 (9 8 7 5 4 3 2 1 0)]

Alex Miller (Clojure team)20:09:27

there isn't a function to do this, and I would encourage you to structure your data in a way that you don't need it

schmee20:09:51

agree, that’s why I was asking for more context 🙂

vncz20:09:36

All right! Thanks for the help. I'm trying to get some proficiency in Clojure and I'm trying to implement the "Game of Pure Strategy" game.

vncz20:09:21

In the game, there's a step where I need to draw an element from a deck and then remove it (the removal is part of the construction of the "next state")

vncz20:09:39

This is the initialState I start with and then I have a function gameStep that takes such state and provides the new one

vncz20:09:40

I need to draw a card from all the decks, do a comparison and then call the gameStep again (which has a guard clause of course, and is (= 0 (count (:bountyDeck currentState)

vncz20:09:55

If there'a better way — I'm all for it!

schmee20:09:57

one idea from the top of my head is to model the deck as a map from index to card

schmee20:09:25

then the “get and remove” would be as easy as calling (dissoc deck index)

vncz20:09:47

Something like this?

vncz20:09:08

:thinking_face: I'm curios now, why is it ok for maps to get and remove and not so ok for vectors?

Alex Miller (Clojure team)20:09:26

because maps support removal by key and vectors don't

vncz20:09:45

Ah ok — so the idea is not that bad, I'm just using the bad data structure if I'm following

schmee20:09:52

exactly :thumbsup:

vncz20:09:54

but I'm on the same situation now?

vncz20:09:39

I get a new map, but not the removed value — I'd still need to lookup the element value manually

schmee20:09:40

do (get testMap :1) and store that before you dissoc

vncz20:09:58

Got it, and I'm assuming there's no way to do that in one shot with a built in function

vncz20:09:48

Wouldn't a set be even better?

schmee20:09:02

I don’t think so, no

schmee20:09:14

a set is not ordered, I assume the deck is?

vncz20:09:23

No, I do not care about the order

schmee20:09:05

then set is better, yes! sets use disj instead of dissoc, fyi

vncz20:09:29

Ok, let's then!

vncz20:09:38

Ok I got it working. It's terrible, I know, but it's a start. I'll go from there, thanks!

vncz14:09:10

@schmee Figured out I can use the threading macro to clean up some stuff and make it a little bit better

p-himik14:09:01

(= 0 (county bountyDeck)) should be replaced with (empty? bountyDeck). Or just swap the branches in if and replace the condition with (seq bountyDeck). Unless, of course, boundyDeck is some custom collection type. When you want to get-in a path of only keywords, you can just use the -> macro: (-> current-state :firstPlayer :deck). Both shorter and faster. update-in with a path of a single item can be just update. (update-in [:bountyDeck] (disj drawn-card)) and the lines below it don't od anything - you should remove the parentheses around the call to disj.

vncz14:09:50

@U2FRKM4TW Thanks for the suggestions — I do have a doubt about the last line indeed. I made a mistake in the copy paste

vncz14:09:56

What I'm doing at the moment is (update-in [:secondPlayer :deck] #(disj % second-player-card))

vncz14:09:09

Using an anonymous function, but if there's a way to avoid that, even better. I haven't yet found one.

p-himik14:09:36

Just use (update-in [:secondPlayer :deck] disj second-player-card)

p-himik14:09:54

update-in internally does (apply f v args).

vncz14:09:05

Ah ok I understand, this is because update-in takes an additional array of parameters that will feed to the function after the main arugment

p-himik14:09:06

When in doubt, check the source code. :)

vncz14:09:08

Didn't notice that

vncz14:09:39

Ok so this is what I have now

vncz14:09:17

Glad to hear that!

vncz14:09:47

I've also reversed the if order as suggested and avoided the new state symbol

p-himik15:09:38

One tiny thing - it's better to have a new line right after [current-state] to avoid shifting everything to the right too much and creating too much empty space on the left.

schmee15:09:22

looking good! :thumbsup: small nitpick/fyi: variables and keywords in clojure are usually `:snake-case` rather than `:camelCase`

vncz15:09:26

@schmee I've changed them all 🙂

vncz15:09:35

@U2FRKM4TW Great suggestions, it looks way better now

p-himik15:09:45

I wouldn't recommend using maps-within-maps destructuring - it's much harder to read than a single level destructuring or a maps-within-vector destructuring.

p-himik15:09:39

I would maybe write it instead as

(defn game-step [{:keys [bounty-deck first-player second-player] :as current-state}]
   ...)
And then you don't really need to create first-player-deck - just use (:deck first player) right where it's needed.

vncz15:09:58

Makes sense — better not go too crazy I guess

vncz21:09:08

First lesson learned: accessing nested maps can be a pain, so better keep data structures as simple as possible troll

schmee21:09:48

looks good, definitely on the right track! :thumbsup:

emccue21:09:13

@vincenz.chianese If you want to remove by index and get an item at the same time, you can always use juxt

emccue21:09:53

(juxt :1 #(dissoc % :1))

emccue21:09:04

will return a vector that is basically

emccue21:09:26

[(:1 ds) (dissoc ds :1)]

emccue21:09:40

when called on whatever structure

vncz21:09:59

Oh interesting

emccue21:09:05

juxt is generally pretty useful if you want to do things "all in one shot" (logically, there might be multiple passes over data)

vncz21:09:53

I can see! Calls multiple functions on the same arg right?

emccue21:09:16

(let [[value new-coll] ((juxt #(nth % 3) #(remove (partial = 3) %)) collection)])

emccue21:09:26

and returns the result in a vector

jaide23:09:24

That’s weird right?

user=> (map #(doto % println) [1 2 3])
(1
2
3
1 2 3)

seancorfield23:09:19

What's weird about it?

jaide23:09:56

Shouldn’t it be outputting?

1
2
3
(1 2 3)

jaide23:09:14

The side effect should trigger before the result is returned for printing right?

seancorfield23:09:26

map produces a lazy seq, so the REPL starts to print a sequence ( then it has to realize the first chunk of it -- which causes the function to be called on three elements -- and then it can print that chunk.

seancorfield23:09:53

When you mix laziness and side-effects, you should expect interleaving 🙂

seancorfield23:09:53

Try (map #(doto % println) (range 40)) and you'll see chunking in action.

jaide23:09:19

I suppose that makes sense. Sounds like I’m picking a terrible test case to showcase Clojure’s ability to map over vectors, lists, and hash-maps.

seancorfield23:09:39

It'll print ( then 0 to 31 (from the println) then the result of the first chunk -- a line with 0 1 2 3 .. 31 -- then it'll print 32 up to 39 (from the println) then the next chunk of the result -- a line with 32 33 .. 39 and the )

jaide23:09:43

I’ll just switch to a pure function

jaide23:09:46

I’ll try it

seancorfield23:09:49

Never mix side-effects and laziness!

seancorfield23:09:14

If you use mapv it is eager and you won't get interleaving.

seancorfield23:09:52

(but, yes, a pure function would be a better demonstration -- otherwise you have to explain laziness and chunking!)

jaide23:09:13

I’m surprised I haven’t run into that before, but thanks for explaining it.

jaide23:09:34

Although I suppose I usually print the seq or use a do-seq for that

vncz14:09:10

@schmee Figured out I can use the threading macro to clean up some stuff and make it a little bit better