Fork me on GitHub
#beginners
<
2020-04-24
>
tjb00:04:48

im having trouble trying to articulate in clojure how to code the following function. given a list of keywords, i want to iterate over that list and check if a map contains that keyword. if it does i want to do an action (in my case assoc it with a new value). can anyone give me a direct to look in? i have tried map and for and keep hitting a wall

andy.fingerhut00:04:33

If you know the key/value pairs you want to merge in ahead of time and always add them with that value regardless of whether they are already in the original map, or not, then merge can do that. Since you say you want the new values to be used only if the key is already present in the original map, that isn't merge. reduce is a pretty general looping construct, something like (reduce original-map (fn [cur-map k] (if (contains? cur-map k) (assoc cur-map k new-val) cur-map) list-of-keys) is the general kind of pattern that seems like it would work for you.

andy.fingerhut00:04:46

except I messed up the order of arguments to reduce there. original-map should be just before list-of-keys

noisesmith00:04:52

+1 for the reduce approach

tjb01:04:35

andy this is brilliant!!!

tjb01:04:36

thank you!

tjb02:04:25

reshred.system.server=> 
(def mm {:status 200, :body nil})
#'reshred.system.server/mm
reshred.system.server=> 
(assoc mm :body body)
Syntax error compiling at (form-init8744011881109093941.clj:1:1).
Unable to resolve symbol: body in this context
class clojure.lang.Compiler$CompilerException
Show: AllClojureJavaToolingDuplicates
reshred.system.server=> 
(println mm)
{:status 200, :body nil}
nil

tjb02:04:44

why cant i update or assoc mm ? i get an error every time

andy.fingerhut02:04:18

The Unable to resolve symbol: body in this context is the part of that error message that stands out to me.

tjb02:04:24

reshred.system.server=> 
(update mm :body {:id 1})
{:status 200, :body nil}

tjb02:04:28

another example ^

tjb02:04:32

the :body does not update

andy.fingerhut02:04:18

Try (assoc mm :body {:id 1})

tjb02:04:48

hmmmmmmmm

tjb02:04:50

interesting

andy.fingerhut02:04:03

The error message I called out above is because the symbol body had no value defined for it at the time you tried to evaluate (assoc mm :body body)

tjb02:04:25

so if the keyword is nil it will always bomb?

tjb02:04:36

mistype i understand

tjb02:04:52

the body variable is empty and not define

andy.fingerhut02:04:36

The reason that (update mm :body {:id 1}) returned the same value as mm is different -- it is because update and assoc take different kinds of arguments -- update takes a function and optionally arguments for it, and calls that function on the original value associated with the key

andy.fingerhut02:04:37

A Clojure map data structure can behave like a function, though, which is why there was no error. A Clojure map behaves like a function from its keys, returning their corresponding values.

andy.fingerhut02:04:47

And sorry if I am inundating you with too much, but one more REPL session for you to think about, demonstrating that assoc is a pure function, and the map that is the value of mm is immutable, and not changed by the call to assoc :

👍 1
✔️ 1
andy.fingerhut02:04:50

user=> (def mm {:status 200, :body nil})
#'user/mm
user=> (assoc mm :body {:foo 1 :bar 2})
{:status 200, :body {:foo 1, :bar 2}}
user=> mm
{:status 200, :body nil}

andy.fingerhut02:04:25

assoc takes one map as input, and returns a new one, which usually shares memory and most of its key/value pairs with the input map.

Steiner02:04:49

hello,everyone. I'm trying to make Number of combinations with FP,but I have no idea.Any one can help me? just like this function (defn C [nums n] ...) means take n from nums into making Number of combinations

andy.fingerhut02:04:07

So you don't want a function like (num-combination 5 3) that returns 10. You want a function like (combinations ["a" "b" "c" "d" "e"] 3) that returns a collection of 10 3-element collections, all different?

Steiner02:04:24

yes,that's it

andy.fingerhut02:04:16

Do you already have an idea of how you might solve it, in words, perhaps using some kind of recursive approach?

Steiner02:04:35

sorry,not yet

Steiner02:04:05

but i have thought it may be recur

andy.fingerhut02:04:42

There are definitely multiple ways to solve this, and if this is part of an exercise to learn how to solve such problems, I wouldn't want to just give the whole answer away all at once 🙂

andy.fingerhut02:04:32

Clojure's recur can be used for some kinds of recursive approaches, but not all of them. I suspect it would be simpler to ignore the existence of recur at first, and think of that as an optimization that might be useful later, after you have something working that doesn't use recur

andy.fingerhut02:04:38

I can give a significant hint for one way to solve it recursively, first just in words rather than writing code for it.

Steiner02:04:03

I don't mind that

andy.fingerhut02:04:41

If you think of all of the 3-element subsets of the 5-element set #{"a" "b" "c" "d" "e"} some of them contain the element "a", and some do not.

andy.fingerhut02:04:44

Of all of the 3-element sets that do contain "a", how many other elements besides "a" must they have in them?

Steiner02:04:00

sorry,I haven't used to communicate with English,please hang on

andy.fingerhut02:04:15

No problem. Take your time.

andy.fingerhut02:04:01

Do you mean like: given 5 people, how many different sets of 3 people can you select from those 5?

andy.fingerhut02:04:50

And you already know the formula from math for this, probably?

Steiner02:04:58

let's start a thread??

tjb02:04:31

@andy.fingerhut thank you for the insight!

tjb02:04:27

i am not sure i am doing this tranformation at the right place or do i have to transform that request on the actual POST route for each POST request

sroller04:04:39

I'm trying to write the result of a lazy sequence in to a file. I tried

sroller04:04:12

(spit "/temp/logbook.edn" (doall (xml-seq logbook)))

sroller04:04:48

does anybody have an idea how to get around this?

seancorfield04:04:38

spit just calls str on the thing you pass in. doall realizes it all, but it's still a LazySeq object.

seancorfield04:04:57

What format do you want the data written out as?

alexmiller04:04:21

pr-str I think will help

seancorfield04:04:44

Yeah, I was about to suggest that but wondered what @sroller expected in the file...

alexmiller04:04:08

I presume a list

seancorfield04:04:10

@tjb He's a rock star! /cc @dpsutton

👍 5
🙏 2
🍻 2
mario-star 1
sroller05:04:34

thank you guys. I was eventually successful with (spit "file.edn" (with-out-str (clojure.pprint/pprint xml)))

sroller05:04:39

I'll try the pr-str method.

sroller05:04:55

my underlying problem: I'm trying to import 700mb xml from a my discontinued sporttrack3. It contains all my activities + GPS data since 2004. I'm playing around with Clojure since 2015. I'm finally getting serious with this one. (Did I mentioned I hate XML?)

sroller05:04:26

My approach is to suck the entire file in and then work over the structure created with xml/parse. I found zip/xml-zip but I doesn't yield the results I'm expecting. I'm still at the very beginning with this.

sroller05:04:11

ok - so pr-str obviously worked. I like the output of pprint better though. I can find better my way around. Need to sleep now, it's 1:17am. Cu l8ter.

sroller05:04:20

thanks again.

Daniel Stephens10:04:03

Is there something like test categories in clojure through meta data or something or do people just use different namespaces?

andy.fingerhut10:04:34

Leiningen offers the ability to mark deftest forms with keywords, and then select subsets of deftest forms whose tests should be run via a function given that keyword, and returns true or false, specified in the project.clj file.

andy.fingerhut10:04:14

By namespaces is another way. I would guess there are others used by at least some people, but don't have any kind of exhaustive list to provide.

Daniel Stephens10:04:55

Thanks Andy, do you know where that first one is documented, using a keyword? That's what I was looking for but I couldn't track it down

kelveden10:04:20

lein help test is a good start.

👍 1
Daniel Stephens10:04:48

ahh perfect! cheers

👍 1
Chris K11:04:17

Quick question about clojure equality check I am using emacs and I have flycheck-clj-kondo enabled and it gave me this message which I didn't understand

(= "X" X-find)

Chris K11:04:35

Single operand use of clojure.core/= is always true

Chris K11:04:06

wait nevermind sry guys, I forgot to update the file.... 😞

ashnur11:04:42

what is an idiomatic way to build a hashmap from two other hashmap and a list of keys such ad that for each key there is an order (not always the same) from which of the two other hashmaps should be tried first, then the next, then nil for value

ashnur11:04:09

I can type it out but I get the feeling this is exactly something I should be able to do more easily with clojure

andy.fingerhut11:04:48

merge is more restricted than that, in that it always considers all keys in the order that you give them, with the last map having a particular key having its value appear in the result.

borkdude11:04:18

@ashnur I think concrete input/output examples would be helpful here

☝️ 1
andy.fingerhut11:04:46

In the behavior you wish to have, do you want to be able to say that for key :foo, the order of precedence is "1st map, then 2nd map, then 3rd map", but for key :bar you want "2nd map, then 1st map, then 3rd map" ?

andy.fingerhut11:04:42

Hmmm. Never mind the "3rd map" part of my question, since you asked about only 2 input maps.

ashnur11:04:17

I don't have the output encoded, it wouldn't be necessary to do the work if I had, but I can type out the logic

ashnur12:04:29

input: [:key1, :key2, :key3] and assuming there are to hashmaps map1 map2 output would be {:key1 (or (get map1 key1) (get map2 key1)), :key2 (get map2 key2) :key3 (or (get map2 key3) (get map1 key3)) note how the order for keys can change or sometimes just one of the maps are used. But there are only two maps and the keys are the same

ashnur12:04:13

because there are dozens of keys, even if it's easy to type it out, it's really hard to read and I would say it's not clean code

ashnur12:04:04

basically, I am preparing a hashmap to be sent for a remote API and I need to use user input (map1) or app-state (map2)

andy.fingerhut12:04:18

So do you expect there to be another input, which for each key somehow describes whether map1 should take precedence, or map2 should?

andy.fingerhut12:04:11

For example, the input could contain two maps, and two collections of keys ks1 and ks2, where keys in ks1 prefer to get their value from map1, and keys in ks2 prefer to get their value from map2?

ashnur12:04:36

yes, something needs to say in what order should the maps be tried

ashnur12:04:53

I can even suspect that a third map might not be unlikely in some cases, although this is not the case at the moment

ashnur12:04:22

so that's why I didn't want to use something like what you suggest @andy.fingerhut because if it turns out that there will be 3 maps to be read from, this breaks down

andy.fingerhut12:04:07

I can't suggest how to write a function to do this, unless I know what the inputs are. If you want to extend it to a 3rd map, then I'm even more in the dark what form you want to specify the input in.

ashnur12:04:01

@andy.fingerhut I don't understand, my question I believe is exactly about the inputs. At the moment I just know what it needs to be there, but apart from 2 (or more) maps from which the keys have to come, and the actual keys, there is no input

ashnur12:04:36

so the input you asking me for is exactly the same thing I am asking about: how to encode the logic of which order the maps should be read?

andy.fingerhut12:04:05

So you want to call the function with parameters like (frobnifier [:key1 :key2 :key3] map1 map2), and it somehow magically figures out that :key1 should prefer the value from map1?

andy.fingerhut12:04:31

I'd like to figure out how to avoid using magic.

ashnur12:04:28

absolutely what I am aiming at 🙂 I think we agree about this

ashnur12:04:05

but there are dozens of keys and for each key there might be different order, I need to encode this order somehow, I want to make it explicit and easy to read and easy to extend

ashnur12:04:18

not simple necessarily 🙂

andy.fingerhut12:04:53

So here is a different function than the one I hint at above, I'll call it prefer-keys. It takes map1 and a collection ks1 whose values should be taken from map1 if they exist there, otherwise from map2. It also takes map2 and a collection ks2 of keys whose value should preferentially be taken from map2. Assumptions I will make for the moment, but you might prefer different behavior: (a) no key appears in both ks1 and ks2, and (b) the output map contains only keys in ks1 or ks2. If a key is in an input map that is in neither ks1 nor ks2, it will not be in the returned map.

andy.fingerhut12:04:20

(defn prefer-keys [m1 ks1 m2 ks2] (merge (select-keys m2 ks1) (select-keys m1 ks1) (select-keys m1 ks2) (select-keys m2 ks2))) user=> (prefer-keys {:a 1 :b 2} [:a :e] {:a -1 :c -3 :d -4} [:b :c]) {:a 1, 😛 2, :c -3}

👍 1
ashnur12:04:32

the assumption sound correct to me

ashnur12:04:06

hah, select-keys! thanks

NoahTheDuke15:04:35

is it possible to test a macro that throws an error if incorrectly written? if it was a normal function, I could write (is (thrown? IllegalArgumentException. TEST)), but when it's a macro, the error is thrown at expansion/compile

noisesmith15:04:20

you can use eval inside is

noisesmith15:04:35

or macroexpand

noisesmith15:04:58

(cmd)user=> (deftest foo-test (is (thrown? clojure.lang.ArityException (macroexpand '(when)))))
#'user/foo-test
(ins)user=> (foo-test)
nil

NoahTheDuke18:04:44

This is interesting. I've been playing with this, trying to get it to work, and the only way to "pass the test" is to say (thrown? clojure.lang.Compiler$CompilerException (macroexpand '(...)))

noisesmith18:04:03

you could also try/catch on the Compiler$CompilerException and then assert about the cause

NoahTheDuke18:04:28

okay, i'll give that a whirl

noisesmith18:04:53

hmm - but to really ensure the test runs and can fail, you should return the exception from the try/catch, then assert on that outside the catch

NoahTheDuke18:04:12

got that working. further testing has shown that using macroexpand in the repl works, but not in a test file run with lein test. Using eval works with lein test as long as I fully-qualify the name: cond-plus.core/cond+

NoahTheDuke18:04:16

thanks so much for the help

noisesmith18:04:07

macros are happy to expand to functions / macros that are not (yet?) in scope, so ensure that all the code that is in the expansion is required in the context you are using it (clojure won't enforce this in the way it would for functions)

noisesmith18:04:58

of course this turns into an error at runtime, it's just that it won't be reported in the way we are used to

noisesmith15:04:07

and the failing version

(ins)user=> (deftest foo-test (is (thrown? clojure.lang.ArityException (macroexpand '(when true)))))
#'user/foo-test
(cmd)user=> (foo-test)

FAIL in (foo-test) (NO_SOURCE_FILE:1)
expected: (thrown? clojure.lang.ArityException (macroexpand (quote (when true))))
  actual: nil
nil

Jose Ferreira15:04:33

I'm having a bit of trouble understanding an error comming from this validation code:

(defn validate-message
  ""
  [params]
  (first (st/validate params message-schema)))

Jose Ferreira15:04:52

class clojure.lang.Keyword cannot be cast to class clojure.lang.Associative (clojure.lang.Keyword and clojure.lang.Associative are in unnamed module of loader 'app')

noisesmith15:04:14

usually that means someone called (assoc x k v) and x was a keyword

noisesmith15:04:33

I'd check the args to validate, perhaps one of those args needs to be a map - the full stack trace (check *e ) will help here

👍 1
Jose Ferreira15:04:03

#error {
 :cause "Unable to resolve symbol: message-schema in this context"
 :via
 [{:type clojure.lang.Compiler$CompilerException
   :message "Syntax error compiling at (/home/jferreira/Develop/projects/clojure/gestbook/src/clj/gestbook/routes/home.clj:24:10)."

Jose Ferreira15:04:16

ok this is nice...didn't know about this stack trace

Jose Ferreira15:04:47

(def message-schema
  [[:name
    st/required
    st/string]
   [:message
    st/required
    st/string]
   {:message "message must contain at least 10 chars"
    :validate (fn [msg] (>= (count msg) 10))}])

Jose Ferreira15:04:00

So the problem is with this def...

Jose Ferreira15:04:27

why wouldn't the evaluator be able to check this symb?

noisesmith15:04:03

what happens when you load that on its own? it could be the definition just isn't in the right order (before usage) in the file?

Jose Ferreira15:04:50

When i eval it from the REPL there is no problem

noisesmith15:04:06

is the definition before the usage in the file?

Jose Ferreira15:04:16

yes, right above it

Jose Ferreira15:04:35

(def message-schema
  [[:name
    st/required
    st/string]
   [:message
    st/required
    st/string]
   {:message "message must contain at least 10 chars"
    :validate (fn [msg] (>= count msg 10))}])

(defn validate-message
  ""
  [params]
  (first (st/validate params message-schema)))

(defn home-page [{:keys [flash] :as request }]
  (layout/render request "home.html" (merge {:messages (db/get-messages)}
                                             (select-keys flash [:name :message :errors]))))

Jose Ferreira15:04:38

calling it directly from the REPL says theres a syntax error....

Jose Ferreira15:04:56

oh, i think i see it, i'm closing the message vector before the map

Jose Ferreira16:04:46

It's working now

NoahTheDuke16:04:08

thanks @noisesmith! very helpful

nick17:04:56

How can I prepend all keys in a given map with "foo"? (def raw {:id 71 :name "whatever"}) ;; expected => {:foo/id 71, :foo/name "whatever"} I would like it to be generic so I can't use #rename-keys with a predefined list of from=>to rules. The bigger task is the transformation of raw jdbc result to the proper Pathom format.

dpsutton17:04:52

your example makes the key have a namespace "bar" but your sentence says you want to prepend "foo"

nick17:04:54

sorry for this confusion. Just updated the message

Darin Douglass17:04:56

(reduce-kv (fn [all key value]
             (assoc all (keyword "bar" (name key)) value)
           {}
           raw)
(->> raw
     (juxt (map (comp (partial keyword "bar") name key)) val)
     (into {}))
something like either of these ^ should work

noisesmith17:04:29

the ->> one needs (juxt ... val) around the comp

👍 1
Darin Douglass17:04:53

heh ya found that out when i decided to actually test it in cider

noisesmith17:04:08

(map (juxt (comp (partial keyword "bar") name key) val)

Darin Douglass17:04:23

could also do

(zipmap (map (comp (partial keyword "bar") name) (keys raw)) (vals raw))

ryan echternacht17:04:25

sometimes my calva -> nREPL connection grinds to a halt (auto formatting fails, the repl itself seems slow). Any thoughts on what’s going on/what I’m doing wrong?

pez18:04:05

@ryan072 I don't think it is you. It is latest Calva. Downgrade to .93 for now. And please PM me if you have done time to help me debug it. I can't reproduce.

nprbst19:04:45

Hello, all you fine Clojurians... I’m new to the Clojure(Script) ecosystem and I’m trying to get my bearings. One thing I’ve noticed as I look around for current state-of-the-art is that it seems many otherwise useful and well-documented libs have their last commits many months or even years ago. Is this an indication of abandonment (as it would’ve be in other ecosystems) or a consequence of the stability of Clojure implementations...as in “it’s working and it will continue working, so no need to touch it”? Any guidance here is much appreciated!

SoV419:04:14

stability

noisesmith19:04:25

new clojure versions very rarely break libraries, so unless something is mainly for interop with a more volatile ecosystem, changes are pretty rare once something works

seancorfield19:04:28

It's also part of the Clojure mindset to a) try to keep the focus of a library small (so you don't get feature creep over time, which also means it can be "done" and need no further updates) and b) avoid breaking changes (fixing bugs is OK, adding features is OK, breaking anything is bad -- so we often use new names for things that are different). @nathan.probst And Welcome!

💯 1
nprbst19:04:23

Thank you. That's what I was hoping to hear!