Fork me on GitHub
#beginners
<
2022-02-25
>
yubrshen00:02:55

How to have variable to hold value to be updated imperatively? I need to have a variable to hold the value of a map that may need to be updated from time to time and globally accessed by functions for computation. What is the proper way to achieve that? While it works with the following code:

(with-local-vars [database {}]
  (var-set database (assoc @database :a 1))
  @database)
But with slight sophistication, the following got an error:
(with-local-vars [database {}]
  (let [lines ["* " "Something else"]]
    (for [line lines]
      (if (re-seq #"^*\s+" line)
        (do
          (var-set database (assoc @database :a 1))
          line)
        line))
    )
  )
Here is the trace of the error.
1. Caused by java.lang.ClassCastException
   class clojure.lang.Var$Unbound cannot be cast to class
   clojure.lang.Associative (clojure.lang.Var$Unbound and
   clojure.lang.Associative are in unnamed module of loader 'app')

                   RT.java:  827  clojure.lang.RT/assoc
                  core.clj:  193  clojure.core/assoc
                  core.clj:  192  clojure.core/assoc
                      REPL:  193  yubrshen.enigma-clj/eval13051/iter/fn/fn
                      REPL:  190  yubrshen.enigma-clj/eval13051/iter/fn
              LazySeq.java:   42  clojure.lang.LazySeq/sval
              LazySeq.java:   51  clojure.lang.LazySeq/seq
                   RT.java:  535  clojure.lang.RT/seq
                  core.clj:  139  clojure.core/seq
            core_print.clj:   53  clojure.core/print-sequential
            core_print.clj:  174  clojure.core/fn
            core_print.clj:  174  clojure.core/fn
              MultiFn.java:  234  clojure.lang.MultiFn/invoke
                pprint.clj:   40  cider.nrepl.pprint/pr/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj: 1977  clojure.core/with-bindings*

yubrshen01:02:21

I found that using atom, the following works:

(def database (atom {}))
(let [lines ["* " "Something else"]]
  (for [line lines]
    (if (re-seq #"^*\s+" line)
      (do
        (swap! database assoc :a 1 )
        line)
      line))
  )
@database
Maybe, this is the proper way of achieve the requirements?

phronmophobic01:02:10

Atoms are probably the most common reference for holding state, but it can be surprising to learn how little state is required. What are the requirements?

dpsutton01:02:08

You are returning a lazy sequence. When you return, the binding is no longer in effect and there is no thing called “database” any longer

👀 1
yubrshen05:02:06

@smith.adriane The requirements is that I need to process a file based on the content of the file, sometimes, I need to update the configurations that will impact the behavior of the processing the rest of the file. The design of using side effect to update a global variable would simplify the communication from process the configuration update based on one line of the file to the processing of the rest of the file. I can think of an alternatiev without update by side effect is to process the content of the file as pipeline keeping the configuration as part of the return from process each line of the file, and input to the processing of the next line of the file. It could be awkward.

phronmophobic05:02:01

The generic, functional way to do that is reduce. There are techniques to define the steps so it's less awkward, but they can take some getting used to. The benefit is that you can decompose the processing into separate, easy to test pieces. It also allows you to flexibly add or update steps if requirements change.

🙏 1
yubrshen13:02:11

Yes, but I need to produce a sequence when processing each line of the file, rather just the aggregation of the all lines. reduce can do the aggregation for sure. But I'm looking for a function that can accumulate the state, but produce an element in the sequence corresponding to each line of input. There might be such function, but I forget, its name now. Please remind me of that.

yubrshen13:02:31

Yes, I figure it out, I can always keep the desired element-wise output into a sequence as part of the accumulation, and then extract the sequence at the end of the reduce. What if the input sequence is lazy and infinite, that the reduce will not stop?

emccue15:02:26

@U2QGRCMSM If you need to stop a reduce early, you can use (reduced ...)

🙏 1
yubrshen01:02:21
replied to a thread:*How to have variable to hold value to be updated imperatively?* I need to have a variable to hold the value of a map that may need to be updated from time to time and globally accessed by functions for computation. What is the proper way to achieve that? While it works with the following code: (with-local-vars [database {}] (var-set database (assoc @database :a 1)) @database) But with slight sophistication, the following got an error: (with-local-vars [database {}] (let [lines ["* " "Something else"]] (for [line lines] (if (re-seq #"^*\s+" line) (do (var-set database (assoc @database :a 1)) line) line)) ) ) Here is the trace of the error. 1. Caused by java.lang.ClassCastException class clojure.lang.Var$Unbound cannot be cast to class clojure.lang.Associative (clojure.lang.Var$Unbound and clojure.lang.Associative are in unnamed module of loader 'app') RT.java: 827 clojure.lang.RT/assoc core.clj: 193 clojure.core/assoc core.clj: 192 clojure.core/assoc REPL: 193 yubrshen.enigma-clj/eval13051/iter/fn/fn REPL: 190 yubrshen.enigma-clj/eval13051/iter/fn LazySeq.java: 42 clojure.lang.LazySeq/sval LazySeq.java: 51 clojure.lang.LazySeq/seq RT.java: 535 clojure.lang.RT/seq core.clj: 139 clojure.core/seq core_print.clj: 53 clojure.core/print-sequential core_print.clj: 174 clojure.core/fn core_print.clj: 174 clojure.core/fn MultiFn.java: 234 clojure.lang.MultiFn/invoke pprint.clj: 40 cider.nrepl.pprint/pr/fn AFn.java: 152 clojure.lang.AFn/applyToHelper AFn.java: 144 clojure.lang.AFn/applyTo core.clj: 667 clojure.core/apply core.clj: 1977 clojure.core/with-bindings*

I found that using atom, the following works:

(def database (atom {}))
(let [lines ["* " "Something else"]]
  (for [line lines]
    (if (re-seq #"^*\s+" line)
      (do
        (swap! database assoc :a 1 )
        line)
      line))
  )
@database
Maybe, this is the proper way of achieve the requirements?

yubrshen05:02:06
replied to a thread:*How to have variable to hold value to be updated imperatively?* I need to have a variable to hold the value of a map that may need to be updated from time to time and globally accessed by functions for computation. What is the proper way to achieve that? While it works with the following code: (with-local-vars [database {}] (var-set database (assoc @database :a 1)) @database) But with slight sophistication, the following got an error: (with-local-vars [database {}] (let [lines ["* " "Something else"]] (for [line lines] (if (re-seq #"^*\s+" line) (do (var-set database (assoc @database :a 1)) line) line)) ) ) Here is the trace of the error. 1. Caused by java.lang.ClassCastException class clojure.lang.Var$Unbound cannot be cast to class clojure.lang.Associative (clojure.lang.Var$Unbound and clojure.lang.Associative are in unnamed module of loader 'app') RT.java: 827 clojure.lang.RT/assoc core.clj: 193 clojure.core/assoc core.clj: 192 clojure.core/assoc REPL: 193 yubrshen.enigma-clj/eval13051/iter/fn/fn REPL: 190 yubrshen.enigma-clj/eval13051/iter/fn LazySeq.java: 42 clojure.lang.LazySeq/sval LazySeq.java: 51 clojure.lang.LazySeq/seq RT.java: 535 clojure.lang.RT/seq core.clj: 139 clojure.core/seq core_print.clj: 53 clojure.core/print-sequential core_print.clj: 174 clojure.core/fn core_print.clj: 174 clojure.core/fn MultiFn.java: 234 clojure.lang.MultiFn/invoke pprint.clj: 40 cider.nrepl.pprint/pr/fn AFn.java: 152 clojure.lang.AFn/applyToHelper AFn.java: 144 clojure.lang.AFn/applyTo core.clj: 667 clojure.core/apply core.clj: 1977 clojure.core/with-bindings*

@smith.adriane The requirements is that I need to process a file based on the content of the file, sometimes, I need to update the configurations that will impact the behavior of the processing the rest of the file. The design of using side effect to update a global variable would simplify the communication from process the configuration update based on one line of the file to the processing of the rest of the file. I can think of an alternatiev without update by side effect is to process the content of the file as pipeline keeping the configuration as part of the return from process each line of the file, and input to the processing of the next line of the file. It could be awkward.

Hao Liang06:02:17

Hi. How to simplify the following code?

(defn data-changed? [data new-data]
  (or (not= (:a data) (:a new-data))
      (not= (:b data) (:b new-data))
      (not= (:c data) (:c new-data))))

hiredman07:02:41

Isn't that just not=

lsenjov08:02:56

select-keys with a not=

lsenjov08:02:21

There could be more than just those three keys

Hao Liang09:02:33

Ahh, got it. Thanks.🙂

bastilla10:02:24

Again a day where Clojure makes me feel stupid. I can't find the error in a very basic function. (You could test this phenomena yourself via the code snippet at the very end.) The fn is just transforming a vector of maps into a map according to the map values. For instance, [{:id "aaaa" :c "cccc" :d "ddddd"} {:id "bbbb" :c "xxxx" :d "something else"}] is supposed to lead to {"aaaa" {"cccc" {:id "aaaa" :c "cccc" :d "ddddd"}}, "bbbb" {"xxxx" { {:id "bbbb" :c "xxxx" :d "something else"}} when called with :id :c as args. Here is the function that should do the job but doesn't: (defn vec-to-map-2 [vec-of-maps kw & kws] (let [kws-vec (vec (conj kws kw)) log (println (str "kws: " kws-vec))] (loop [vec vec-of-maps map {}] (if (empty? vec) map (let [first-map (first vec) check-1 (print (str "map: " first-map)) check-2 (print (str "a-kws: " kws-vec)) index-path (map (fn [x] (get first-map x)) kws-vec) check-3 (println (str "index-path: " index-path)) ext-map (assoc-in map index-path first-map) check-4 (println (str "ext-map: " ext-map))] (recur (rest vec) ext-map)))))) (5 Lines are just logging to find my mistake.) Error is: Instead of taking/getting the map's values the fn uses the map's keys:

kws: ["table_name" "column_name"]

map: {"ordinal_position" 14, "is_identity" "NO", .... ABBREVIATED  "table_name" "spiel", "numeric_precision_radix" nil, "generation_expression" nil, "data_type" "boolean", "column_name" "spielleitung", .... ABBREVIATED}
a-kws: ["table_name" "column_name"]
index-path: ["table_name" "column_name"]

ext-map: {"table_name" {"column_name" {"ordinal_position" 14, "is_identity" "NO", "character_set_name" nil, "scope_schema" nil, "character_octet_length" nil, "table_name" "spiel", "numeric_precision_radix" nil, "generation_expression" nil, "data_type" "boolean", "column_name" "spielleitung", .... ABBREVIATED}}}
See the "index-path" is ["table_name" "column_name"]. But it should be ["spiel" "spielleitung"]. Here comes the funny part. When I do this step by step, it works! (defn vec-to-map-step-by-step-test [kw & kws] (let [test-map (first test-data) kws-vec (vec (conj kws kw)) index-path (map (fn [x] (get test-map x)) kws-vec) ext-map (assoc-in {} index-path test-map)] (println (str "map: " test-map)) (println (str "kws-vec: " kws-vec)) (println (str "index-path: " index-path)) (println (str "ext-map: " ext-map)))) Called via (vec-to-map-step-by-step-test "table_name" "column_name") the correct (!) output is:
map: {"ordinal_position" 14, "is_identity" "NO", "character_set_name" nil, "scope_schema" nil, "character_octet_length" nil, "table_name" "spiel", "numeric_precision_radix" nil, "generation_expression" nil, "data_type" "boolean", "column_name" "spielleitung", .... ABBREVIATED}

kws-vec: ["table_name" "column_name"]

index-path: ("spiel" "spielleitung")

ext-map: {"spiel" {"spielleitung" {"ordinal_position" 14, "is_identity" "NO", "character_set_name" nil, "scope_schema" nil, "character_octet_length" nil, "table_name" "spiel", "numeric_precision_radix" nil, "generation_expression" nil, "data_type" "boolean", "column_name" "spielleitung", .... ABBREVIATED}}}
Notice the correct "index-path" ("spiel" "spielleitung") You could test this by this whole snippet here. Call (proof-clj-is-funny) (def test-data [{"udt_schema" "pg_catalog","collation_schema" nil,"numeric_scale" nil,"character_set_schema" nil,"dtd_identifier" "14","maximum_cardinality" nil,"generation_expression" nil,"identity_cycle" "NO","column_default" "false","character_octet_length" nil,"identity_maximum" nil,"identity_minimum" nil,"datetime_precision" nil,"scope_catalog" nil,"collation_catalog" nil,"character_maximum_length" nil,"is_nilable" "NO","is_identity" "NO","character_set_catalog" nil,"numeric_precision" nil,"table_schema" "public","interval_type" nil,"udt_catalog" "playgreen","scope_schema" nil,"numeric_precision_radix" nil,"interval_precision" nil,"identity_start" nil,"domain_catalog" nil,"character_set_name" nil,"is_updatable" "YES","collation_name" nil,"data_type" "boolean","identity_increment" nil,"identity_generation" nil,"table_catalog" "playgreen","column_name" "spielleitung","domain_schema" nil,"table_name" "spiel","is_generated" "NEVER","domain_name" nil,"scope_name" nil,"udt_name" "bool","ordinal_position" 14,"is_self_referencing" "NO"}]) (defn vec-to-map-2 [vec-of-maps kw & kws] (let [kws-vec (vec (conj kws kw)) log (println (str "kws: " kws-vec))] (loop [vec vec-of-maps map {}] (if (empty? vec) map (let [first-map (first vec) check-1 (print (str "map: " first-map)) check-2 (print (str "a-kws: " kws-vec)) index-path (map (fn [x] (get first-map x)) kws-vec) check-3 (println (str "index-path: " index-path)) ext-map (assoc-in map index-path first-map) check-4 (println (str "ext-map: " ext-map))] (recur (rest vec) ext-map)))))) (defn vec-to-map-step-by-step-test [kw & kws] (let [test-map (first test-data) kws-vec (vec (conj kws kw)) index-path (map (fn [x] (get test-map x)) kws-vec) ext-map (assoc-in {} index-path test-map)] (println (str "map: " test-map)) (println (str "kws-vec: " kws-vec)) (println (str "index-path: " index-path)) (println (str "ext-map: " ext-map)))) (defn proof-clj-is-funny [] (vec-to-map-step-by-step-test "table_name" "column_name") (println (str "--------------------------")) (vec-to-map-2 test-data "table_name" "column_name")) In one case, it's correctly taking the map's vals, and when doing the exact same thing in a recur loop it takes the keys. If you have an idea why, please let me know (in a thread) - I'd really appreciate it.

1
bastilla11:02:00

Okay, I found it. I named my local vars a bit misguided. "vec" and "map" as names for local vars are okay, (I think), when you do NOT use (vec ...) and (map ...) from here on (as functions). But I did exactly that. So Clojure applied what was behind the local var "map" and "vec" and applied them as functions, thereby overriding the global Clojure fns vec and map. Stupid mistake. But I guess, one that beginners easily fall into. Thanks for your attention.

2
lsenjov11:02:51

If you're not using kondo, would suggest you do. It gives a warning when you're shadowing a var

bastilla11:02:48

I actually use kondo. I try to find out why I got no warning. Merci.

zendevil.eth11:02:05

can you someone explain simply how with-redefs is different from let?

V11:02:19

You can use it to temporarily change bindings to something else. Eg. foo-fn is a function that always returns 4

(foo-fn)
=> 4

(with-redefs [foo-fn (constantly 2)]
(foo-fn))
=> 2
You temporarily change what foo-fn is binded to. And make the function return something else

V11:02:41

I often use it in testing to mock scenarios and other stuff 🙂

☝️ 1
delaguardo11:02:46

Binding in with-redefs must be defined before.

Ferdinand Beyer12:02:33

Maybe an example helps:

(defn greet [name] (str "Hello, " name))

(defn say-hello [name] (greet name))

(let [greet (constantly "Changed!")]
  ;; here, we have bound 'greet' LOCALLY.
  (greet "zendevil") ; => "Changed!"

  ;; But the "global" var is not changed.
  ;; Calling 'say-hello' will use the global
  ;; 'greet', it does not know the local binding.
  (say-hello "zendevil") ; => "Hello, zendevil"
  )

(with-redefs [greet (constantly "Changed!")]
  ;; Now, every reference to the global var gets
  ;; the overridden value.
  (say-hello "zendevil") ; => "Changed!"
  )

Kimo16:02:20

Hi friends. Looking for a concise way to: • declare tests • short circuit on the first non-nil test • thread off of the test's value • stay out of macro-land What I'd like:

(what-i-want-> {:a 1 :b 2}
  :c dont-do-this
  :b print
  :a dont-because-already-short-circuited) ;; 2
What works, ugly:
(let [{a :a b :b c :c} {:a 1 :b 2}]
  (cond
    c (dont-do-this c)
    b (print b)
    a (dont-because-already-short-circuited a))) ;; 2

craftybones16:02:59

@U02J388JDEG - I think some-> should work for you

Kimo17:02:42

Thanks, I was considering some->, but I think the short-circuit behavior isn't right.

(let [skip (constantly nil)]
    (some-> {:a 1 :b 2}
            :c skip
            :b print
            :a print))

craftybones17:02:05

It skips on false, not nil

craftybones17:02:17

Right. THat won’t work for you

Kimo17:02:10

yeah, not sure how to be concise if I have to coerce every failed test to false

craftybones17:02:51

You could wrap your map in a function and use some-> ?

craftybones17:02:33

Instead of returning nil on a missing key, return false ?

Kimo17:02:42

can sort of imagine that, but it sounds unwieldy. seems like there might be another core macro somewhere. Something to abstract this idea of "try things until one of them works."

craftybones17:02:26

condp has this :>>

craftybones17:02:38

Have you considered that?

Kimo17:02:00

looks promising..

Kimo17:02:37

No luck yet.. any idea how I'd implement condp for the example?

craftybones17:02:46

(condp #(get %2 %1) {:a 1 :b 2}
     :c :>> 'skip
     :b :>> println
     :a :>> println
     :default)

craftybones17:02:06

this ends up printing 2

Kimo17:02:41

that's as far as I got too, haha. reversing the arguments to get is unfeasible from a readability standpoint, though.

Kimo17:02:44

I'll give up for now. Thanks for your insight

craftybones17:02:52

flipping arguments is fairly common

craftybones17:02:08

that flipping of arguments can simply be pulled into a named function

craftybones17:02:17

IS readability the only problem here?

craftybones17:02:34

there is also some-fn

craftybones17:02:26

(->> {:a 1 :b 2}
     ((some-fn :c :b :a))
     (println))

craftybones17:02:01

@U02J388JDEG - this might be what you want. Try it before you truly give up 🙂

Kimo17:02:59

yeah, considered some-fn. but I need to thread a different function for each matching test. Also need a default return value, if no tests pass. So a get predicate doesn't quite fit.

Kimo17:02:10

Ooh, I think this works:

(condp #(%1 %2) {:d 1 :e 2}
  :c :>> 'skip
  :b :>> print
  :a :>> print
  identity :>> print)

Kimo17:02:07

Still a bit hacky, but there's a beauty to it. You convinced me not to give up, haha

👍 1
craftybones17:02:01

(condp {:a 1 :b 2} false
   :c :>> 'skip
   :b :>> println
   :a :>> println
   :default)

craftybones17:02:22

that false is needed and is hacky but this works too

Kimo18:02:31

Wow, that's a strange one. I wonder if it works for expr values which aren't maps.

craftybones18:02:07

maps in Clojure are functions

craftybones18:02:13

it is a multi-arity function

craftybones18:02:28

as are vectors

walterl21:02:53

Is there a core higher order fn than only applies f if its arg x is not nil? Similar to fnil, but avoids calling f at all if x is nil. Something like this:

(defn fwhen
  [f]
  (fn [x]
    (when x
      (f x))))

hiredman22:02:32

#(some-> % f)

🙌 1