Fork me on GitHub
#beginners
<
2021-07-06
>
clj839401:07:41

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

clj839401:07:38

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

hiredman01:07:46

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

clj839401:07:20

i mean mutate as in reassign

hiredman01:07:52

local bindings are also immutable

hiredman01:07:48

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

clj839401:07:48

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, "")
}

clj839401:07:58

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

hiredman01:07:23

Oh, reduce, definitely

hiredman01:07:58

That for loop is a good over arr

clj839401:07:56

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

clj839401:07:00

so like this then?

clj839401:07:42

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 https://github.com/Engelberg/instaparse

seancorfield04:07:39

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

seancorfield05:07:22

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

sheluchin12:07:25

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.

solf12:07:18

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
bnstvn12:07:31

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

(defn seq1 [s]
  (lazy-seq
    (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?

bnstvn12:07:59

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

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

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

indy14:07:32

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

indy14:07:39

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.

indy14:07:17

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

indy14:07:56

b will print realized while a won't

indy14:07:47

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

noisesmith15:07:40

@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

noisesmith15:07:49

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

noisesmith15:07:31

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

☝️ 4
indy15:07:53

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.

noisesmith15:07:37

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

indy15:07:52

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

noisesmith15:07:54

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

noisesmith15:07:49

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

indy15:07:16

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

bnstvn17:07:34

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

Simon13:07:18

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)
     bar-fn
     (map #(str "hello" %)))

Fredrik13:07:57

Or even

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

👍 4
Simon14:07:23

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

Simon14:07:03

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
octahedrion14:07:55

(map (partial str "hello") smileys)

Michael Stokley14:07:02

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

👍 2
Fredrik14:07:27

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
  [b]
  (if criteria (reverse b) b))

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

👍 4
noisesmith15:07:14

or

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