Fork me on GitHub

how would I mutate a string in clojure, get the result, and then mutate the result in a loop?


i think this is doable with a reduce, but the syntax is a bit clumsy and think there may be a better way to do this


java.lang.Strings are immutable, so you aren't going to be mutating them


i mean mutate as in reassign


local bindings are also immutable


I would just use loop/recur to answer the question I think you asking


in an imperative language, I would use a for loop and reassign a variable:

var x = "hello world";
var arr = ["l", "o"];
for (y in arr) {
  x = x.replace(y, "")


what is the best way to do this same operation in clojure?


Oh, reduce, definitely


That for loop is a good over arr


(reduce #(.replace %1 %2 "") "hello world" ["l" "o"])


so like this then?


is there anything like reduce that has the function as the last argument? it becomes a bit difficult to read when the arguments provided are place after the functionality

Drew Verlee03:07:14

Use a thread last macro

Drew Verlee03:07:33

Eg (->> 10 (range) (filter odd?) (map #(* 2 %)) (reduce +))

Alexander Moskvichev04:07:25

Hi. I need to write spec (for reitit route coercion, but maybe it didn't matter). The problem is - my parameter contains square brackets, like ...&page[p]=10... For simple type checking (symbol "page[p]") int? works just fine, but when I try to write a more complex rule using s/def I got errors about namespace. I need to check the type and make the parameter optional. For simple symbols (without those brackets) all work just as needed. i.e. That works (s/def ::level int?) (s/def ::page int?) ;no brackets (s/def ::query-params (s/keys :req-un [::level] :opt-un [::page])) That works but checks only the type inside route data {:get {:parameters {:query :level int? (symbol "page[p]" int?)}... That not (s/def ::level int?) (s/def (symbol "page[p]") int?) ; got error (s/def ::query-params (s/keys :req-un [::level] :opt-un [(symbol "page[p]")]))

Drew Verlee17:07:11

clojure spec is designed to, among other things, validate clojure data, not as way to transform an arbitrary data (strings) into clojure data. For that you would use something like


s/def is a macro so it's not going to evaluate that form.

Alexander Moskvichev05:07:53

The error Assert failed: k must be namespaced keyword or resolvable symbol (c/and (ident? k) (namespace k)) occurs when I try to evaluate the spec in the repl, or when call the router


Yes, because s/def is a macro and it expects a qualified keyword or symbol -- not a list. Macros do not evaluate their arguments.


What is the proper way to align map keys and values if the line is starting to push past the 80 char width limit? I couldn't find anything specific about this in the style guide.


I don’t think you’ll find a one size fits all solution, it depends on the context. If the map starts already highly indented, you might want to change the indentation before it (eg adding line breaks, or refactoring the code). If you have specially long key names, you might want to put keys and values in their own line, with a space between key/value groups. Or even extract the map and put it in a def or a let binding.

👍 2

why is this working for “unchunking” a sequence (from joy of clojure)

(defn seq1 [s]
    (when-let [[x] (seq s)]
      (cons x (seq1 (rest s))))))
i would have thought when extracting out x from (seq s) it should have been realizing the first chunk — it is clearly not the case, but why?


(chunked-seq? (range 1 100)) ;; => true
(chunked-seq? (seq (range 1 100))) ;; => true

(take 1
        (fn [i] (println "realized: " i) i)
        (range 1 100)))
;; realized:  1
;; ...
;; realized:  32
;;=> (1)

(take 1
        (fn [i] (println "realized: " i) i)
        (seq1 (range 1 100))))
;; realized:  1
;;=> (1)


It is not realizing the first chunk when x is destructured because it is inside the lazy-seq macro, if it was say

(defn seq2 [s]
   (when-let [[x] (seq s)]
     (lazy-seq (cons x (seq1 (rest s))))))
then the first chunk would be realized


Also regarding the realized: 1 and so on getting printed, is because the REPL will print the result (the P in REPL) realizing the lazy sequence.


To see the difference, try,

(def a 
   (take 1 (map
             (fn [i] (println "realized: " i) i)
             (range 1 100))))
 (def b 
   (doall (take 1 (map
                   (fn [i] (println "realized: " i) i)
                   (range 1 100)))))


b will print realized while a won't


Oops I think I completely misunderstood the question. Please ignore my answers 🙂


@UGSM2S2CS notice that the printing is happening outside the chunk realization - the first 32 elements of the range are being realized, but the side effect is still only walking 1 at a time, instead of a chunk at a time


what the "dechunk" is doing is clever (as so many things in that book are, it's a great book), but usually the better answer is to ignore chunking and simply not use laziness if the timing of realizing elements effects code correctness


mixing i/o or mutation with laziness is a huge code smell

☝️ 4

Okay I think I found why, so this bit in the map function

(when-let [s (seq coll)]
      (if (chunked-seq? s)
        (let [c (chunk-first s)
              size (int (count c))
              b (chunk-buffer size)]
          (dotimes [i size]
              (chunk-append b (f (.nth c i))))
          (chunk-cons (chunk b) (map f (chunk-rest s))))
        (cons (f (first s)) (map f (rest s)))))
Checks if s is a chunked-seq? but s which is (seq (seq1 (range 1 100))) in our case is false.


right, the chunking is honored by the various lazy-seq producing functions, and they cooperate by also chunking


And hence it uses (cons (f (first s)) (map f (rest s))) to build the lazy-seq and that is one element at a time


what the code does is ignore that chunking, so it doesn't propagate


I would reject any code that dechunks in code review and ask that laziness not be used at all


Hmm yeah, but this was an interesting question from a theoretical point of view


thanks both! i got it eventually. i definitelly wont do anything like this, just wanted to understand whats going on


Is there a better way to do this?

(let [b (foo-fn 42 a)
      smileys (bar-fn b)]
   (for [smiley smileys]
       (str "hello" smiley)))
I think I might be able to use the ->> form?

Michael Stokley13:07:40

(->> (foo-fn 42 a)
     (map #(str "hello" %)))


Or even

(->> a
     (foo-fn 42)
     (map #(str "hello" %)))
if you want to highlight that a is being threaded

👍 4

Actually sorry my example was not representing my code well. I will try again:

(let [b (foo-fn 42 a)
      smileys (if criteria (reverse b) b)]
   (for [smiley smileys]
       (str "hello" smiley)))


The problem is i have an if form inside the threaded

Michael Stokley14:07:40

not everything can be threaded, and sometimes it's better as a let either way

💯 4

(map (partial str "hello") smileys)

Michael Stokley14:07:02

but i'd definitely look at using map instead of for

👍 2

If you really like to thread, you could extract the (if criteria ...) into a function, then call (->> a ... (revert-if-criteria) ...)

🙌 2
👍 2
Michael Stokley14:07:54

(defn make-smileys
  (if criteria (reverse b) b))

(->> (foo-fn 42 a)
     (map #(str "hello" %)))

👍 4


(-> (foo-fn 42 a)
    (cond-> criteria (reverse))
    (->> (map (partial str "hello")))
the various "arrow macros" like ->> , as-> , cond-> etc. were all designed so that they could be used inside ->

oxalorg (Mitesh)19:07:15

I'd personally solve it like this. I break this down into two operations because I don't like mixing in conditionals with "must run" steps. It makes it hard to follow whats going on. Here it's very clear what smileys are (when reading code I want to first know what smileys are, and only then do I care about what order they are in).

(let [smileys (->> a
                     (foo-fn 42)
                     (map #(str "hello" %)))]
    (if criteria
      (reverse smileys)