This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-02-25
Channels
- # aleph (18)
- # announcements (7)
- # asami (18)
- # babashka (15)
- # babashka-sci-dev (79)
- # beginners (61)
- # calva (4)
- # clj-kondo (23)
- # cljfx (16)
- # cljs-dev (6)
- # clojure (63)
- # clojure-bay-area (3)
- # clojure-europe (33)
- # clojure-nl (1)
- # clojure-survey (4)
- # clojure-uk (5)
- # clojurescript (37)
- # conjure (1)
- # cursive (8)
- # datahike (7)
- # datalevin (1)
- # datomic (30)
- # emacs (10)
- # events (2)
- # figwheel (2)
- # fulcro (20)
- # google-cloud (1)
- # lsp (6)
- # luminus (4)
- # malli (5)
- # music (3)
- # nextjournal (1)
- # off-topic (9)
- # other-languages (3)
- # pathom (16)
- # polylith (34)
- # re-frame (14)
- # reagent (19)
- # releases (6)
- # sci (2)
- # shadow-cljs (33)
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?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?
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
@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.
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.
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.
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?
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?@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.
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))))
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.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.
If you're not using kondo, would suggest you do. It gives a warning when you're shadowing a var
can you someone explain simply how with-redefs
is different from let
?
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 elseBinding in with-redefs must be defined before.
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!"
)
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
@U02J388JDEG - I think some->
should work for you
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))
It skips on false, not nil
Right. THat won’t work for you
You could wrap your map in a function and use some-> ?
Instead of returning nil on a missing key, return false ?
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."
condp has this :>>
Have you considered that?
(condp #(get %2 %1) {:a 1 :b 2}
:c :>> 'skip
:b :>> println
:a :>> println
:default)
this ends up printing 2
that's as far as I got too, haha. reversing the arguments to get
is unfeasible from a readability standpoint, though.
flipping arguments is fairly common
that flipping of arguments can simply be pulled into a named function
IS readability the only problem here?
there is also some-fn
(->> {:a 1 :b 2}
((some-fn :c :b :a))
(println))
@U02J388JDEG - this might be what you want. Try it before you truly give up 🙂
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.
Ooh, I think this works:
(condp #(%1 %2) {:d 1 :e 2}
:c :>> 'skip
:b :>> print
:a :>> print
identity :>> print)
(condp {:a 1 :b 2} false
:c :>> 'skip
:b :>> println
:a :>> println
:default)
that false
is needed and is hacky but this works too
maps in Clojure are functions
it is a multi-arity function
as are vectors