Fork me on GitHub
#beginners
<
2020-06-07
>
Hlodowig01:06:45

Hi everybody. I'm trying to run some code that's intended to append data to several CSV files. If I use the REPL it does what it should, but not if I run it in the IDE (Intellij) or an uberjar. I've removed code to the bare essentials and it seems to be a problem with map / for. It won't work even if I remove the spit and just println the values to be written. Would appreciate your help, it's driving me nuts.

andy.fingerhut02:06:43

Both map and for are lazy. Are you using them in a context where their return values are not forced to be evaluated? That is a common mistake when trying out something in the REPL that uses those, which forces the return value to be printed (the P in REPL), but in the context of a full program it is easy to call them in a way that their full sequence is never realized.

thanks2 3
andy.fingerhut02:06:18

Sometimes doall can help in that situation, but there are other ways to force evaluation, too.

bartuka01:06:32

Can you share some code? Just bc you mentioned for and map and working on the REPL, have in mind that both these operations are lazy. Therefore, the REPL realize their values, if you are running this as your last statement in the IDE/uberjar.. it is probably doing nothing (yet). You can check if this is the case by using a doall after calling each of them

Hlodowig01:06:15

(ns maisicuel.core
  (:require [maisicuel.config :as cfg])
  (:gen-class))

(defn write-files [path results]
  (for [app-results results
        :let [today (.format (java.text.SimpleDateFormat. "yyyy-MM-dd") (new java.util.Date))
              app (name (key app-results))
              figures (val app-results)
              line (clojure.string/join "," (vals figures))]]
    (spit (str path app ".csv") (str today "," line "\n") :append true)))

(defn -main []
  (let [output-path "/Users/luisgarcia/Docs/lefort/bosch/usage/"]
    (write-files output-path cfg/results)))

Hlodowig01:06:41

I’ll try doall as soon as I get to my pc.

bartuka01:06:52

Yeap, looks like that is indeed the problem. Consider using doseq instead of for there. I think a straight replacement would work.

✔️ 4
thanks2 4
Hlodowig03:06:23

Worked like a charm, thanks. Got a lot to learn.

🚀 4
Chicão01:06:10

May someone can help? I have one problem when I stop my server in REPL. This is a project to study component.stuartsierr. https://github.com/xico-labs/budget-calculator-api/blob/master/src/budget_calculator_api/core.clj#L21

actual: clojure.lang.ExceptionInfo: Error in component :http in system com.stuartsierra.component.SystemMap calling #'com.stuartsierra.component/stop
{:reason :com.stuartsierra.component/component-function-threw-exception, :function #'com.stuartsierra.component/stop, :system-key :http, :component #
rver{:port 8080, :join false, :storage #budget_calculator_api.components.storage.InMemoryStorage{:storage #object[clojure.lang.Atom 0x4ce4c097 {:status :ready, :val [{:id 1, :bill-title "Gast
ei com remedio", :ammount "R$ 200 moedinhas"} {:id 3, :nome "Ola", :ammount "100 moedinhas"}]}]}}, :system #<SystemMap>}
When I (assoc system :http nill) my server stopped well but when i use (component/stop system) my server crashed

bartuka01:06:40

Your stopped function has additional parens

(defn stopped [server]
  ((.stop server)
   (.join server)))
Try only calling (.stop server) there. (the exception stack trace is chopped right in the portion that was explaining the failure rsrsrs so, I am guessing here)

Chicão01:06:00

I try (.stop server), but had the same problem..

actual: clojure.lang.ExceptionInfo: Error in component :http in system com.stuartsierra.component.SystemMap calling #'com.stuartsierra.component/stop
{:reason :com.stuartsierra.component/component-function-threw-exception, :function #'com.stuartsierra.component/stop, :system-key :http, :component #
rver{:port 8080, :join false, :storage #budget_calculator_api.components.storage.InMemoryStorage{:storage #object[clojure.lang.Atom 0x361a1e86 {:status :ready, :val [{:id 1, :bill-title "Gast
ei com remedio", :ammount "R$ 200 moedinhas"} {:id 3, :nome "Ola", :ammount "100 moedinhas"}]}]}}, :system #<SystemMap>}
And this problem
Caused by: java.lang.NullPointerException: null
 at clojure.lang.Reflector.invokeNoArgInstanceMember (Reflector.java:426)
    budget_calculator_api.components.server$stopped.invokeStatic (server.clj:20)

bartuka01:06:19

@UL618PRQ9 when you start the component you are associng the webserver and them you return the older map without the server

(start [component]
    (let [server (create-server port (:storage component) join)]
      (assoc component :web-server server) component))
Should be:
(start [component]
    (let [server (create-server port (:storage component) join)]
      (assoc component :web-server server)))

❤️ 4
parrot 4
Chicão02:06:14

Thank you!! Worked very well. I understand

👍 4
ludociel03:06:38

i'm back with another clojure / ring doubt:

(defn -main []
  (jetty/run-jetty (logger/wrap-with-logger app) {:port port-number}))
my project is dependent on apache tika parser, so prior to adding it as dependency i had information like running at 3000 port, jetty 9.0 blah blah and all now because i added tika parser and using it, i get this (warnings?) when i do lein run
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See  for further details.
Jun 07, 2020 9:04:15 AM org.apache.tika.config.InitializableProblemHandler$3 handleInitializableProblem
WARNING: J2KImageReader not loaded. JPEG2000 files will not be processed.
See 
for optional dependencies.

Jun 07, 2020 9:04:15 AM org.apache.tika.config.InitializableProblemHandler$3 handleInitializableProblem
WARNING: org.xerial's sqlite-jdbc is not loaded.
Please provide the jar on your classpath to parse sqlite files.
See tika-parsers/pom.xml for the correct version.
nothing else. also i added today logging functionality, i'm not able to see anything on the console other than the above text at this rate. thanks, let me know if you know anything about this.

seancorfield03:06:15

You can solve the SQLite warning by adding the org.xerial/sqlite-jdbc {:mvn/version "3.30.1"} dependency (that's deps.edn format -- lein is a different format).

seancorfield04:06:05

No idea about the J2KImageReader (but, again, that's just another set of dependencies to add).

seancorfield04:06:41

Does your server app still start on port 3000 @jaiimmortal?

ludociel04:06:29

@seancorfield yes yes it does. I honestly don't need sqlite dependency for my project.

seancorfield04:06:54

Then you can just ignore the warnings I guess.

ludociel04:06:16

Yeah but its hiding the jetty/ring logs and the information about it started and running on port and all

seancorfield04:06:09

@jaiimmortal Ah, I just went through this with next.jdbc in testing -- I added log4j2 and the log adapters and then a .properties file to control the logging, restoring what I wanted and suppressing what I did not..

ludociel04:06:47

so I should add log4j2 as a dependency to my project.clj? @seancorfield, and my problem will magically go away?

seancorfield04:06:43

See this commit https://github.com/seancorfield/next-jdbc/commit/9ea87afdd53e0cb3b39263b6d6f702e98a23ad42 (there's some other stuff in there but it should give you an idea)

seancorfield04:06:49

You get a lot of control over the format and it's easy to control configuration via JVM opts and/or environment variables -- so it can be tweaked for dev/test/prod as needed -- and with the log adapters, at least everything flows through the modern library, so you can control it all centrally.

seancorfield04:06:11

(Java logging is a big ol' nightmare, unfortunately)

seancorfield04:06:00

Because you have no other slf4j logging implementation on your classpath, it is defaulting to "no logging". A "simple" solution could just be to add the appropriate slf4j logging library, but you might as well learn how to control all logging at this point...

ludociel04:06:13

As you said it prefers sl4j over the other ones

seancorfield05:06:37

It's probably a simpler, more direct "fix" for this particular problem. But Java logging is a giant mess, whatever you do 😐

seancorfield05:06:30

We've gone down a number of paths at work with logging, and we ended up finally switching to log4j2 (after spending a long time with Timbre -- an all-Clojure library -- and its adapters along the way).

lispyclouds05:06:07

Just out of curiosity, what were the issues that were faced with Timbre? Im planning to use it in some serious places and would love to know of its shortcomings.

seancorfield05:06:51

Ask me on Monday when I'm at work (on my phone now).

3
lispyclouds16:06:37

Hello @seancorfield 😄 would now be a good time?

seancorfield17:06:02

Hiya @U7ERLH6JX! Yup, I'm back at work now. Timbre is a funny beast. The taoensso libraries are all heavily intertwined so if you pull in one of them, you often end up with several of them, so you get a lot of random code added to your code base. It doesn't address the fundamental problem of Java logging (that there are so many libraries) and in fact just adds another one on top -- you end up having to use a third-party adapter to route slf4j through Timbre, and then you still need to add all the slf4j adapters to route other logging libraries through slf4j, which in turn is then routed through Timbre (as I recall -- it's been a while since we switched). slf4j is problematic anyway because of the default null-logging behavior, and also because it treats FATAL and ERROR as the exact same level so you lose that distinction for all of your logging. Timbre's configuration isn't like any other Java logging library -- and it has a huge number of "bells & whistles" so you can be lured down a path of complex configuration that is not managed the same way that ops folks are used to. About the only positive we found was that it can easily be reconfigured programmatically, on-the-fly.

seancorfield17:06:11

In the end, we only found ourselves using the programmatic configuration at runtime in just one situation: for some specific testing, and we realized that log4j2 supported that change via JVM options which was sufficient. We found switching from Timbre and a third-party slf4j adapter (and slf4j itself and all its Java logging adapters) over to log4j2 (and its adapters) to simplify our ops-level configuration and our apps became "better JVM citizens", in addition to the overall simplification in code (using clojure.tools.logging, which a lot of our dependencies already assumed, with log4j2 specified as the default factory).

lispyclouds18:06:09

this is really really enlightening! Since most of the cases i had simple usage i didn't run into these issues but now that i think of it its quite a thing! log4j2 by the sounds of it seems more simple and I would try that out now! Thanks a lot for the valuable pointers! 😄 🙏:skin-tone-3:

seancorfield18:06:32

It's just unfortunate that Java logging is a giant tire fire 🔥 and I understand why Timbre exists -- and if you had a pure Clojure app that didn't include a single dependency that relies on Java logging then Timbre could be awesome... but I can't imagine a real-world app that has zero Java logging dependencies 😐

ludociel05:06:25

After two hours. I added [org.slf4j/slf4j-simple "1.6.2"] and it starts working as expected.

deactivateduser05:06:26

FWIW I prefer logback for logging over log4j, though most of my experience with log4j has been with v1. slf4j also provides ways to force calls to other Java logging libraries to pass through it instead, and IMLE those mechanisms seem to work well. You can see an example of how to set that up here: https://github.com/symphonyoss/bot-unfurl/blob/master/src/bot_unfurl/config.clj#L28-L30 and here: https://github.com/symphonyoss/bot-unfurl/blob/master/project.clj#L39-L43

deactivateduser05:06:00

As @seancorfield mentioned, the Java logging landscape is a radioactive dumpster fire (my words, not his!), and the best you can hope for is to punt to something like slf4j to do its best to corral disparate incompatible logging calls from your various dependencies into a single logging mechanism.

seancorfield05:06:56

slf4j treats fatal and error as the same level so that is an instant black mark in my book.

seancorfield05:06:45

(I'm going to sleep now!)

deactivateduser05:06:06

Can log4j2 intercept calls to other logging libraries though (`clogging`, jul, etc.)? That’s a substantially more important requirement in my book…

deactivateduser05:06:53

Also, the slf4j team have a reasonable argument as to why FATAL is a different concept than logging level: http://www.slf4j.org/faq.html#fatal. Not saying everyone has to agree with it, mind you, but it was a conscious choice.

ludociel06:06:25

i started with clojure/ring day before yesterday without prior experience in java for doing a quick project involving tika parser. didn't expect to go down such a rabbit hole

deactivateduser06:06:37

Sorry. 😞 Logging is one of those things in Java that’s just had a long and sad history…

deactivateduser06:06:59

The good news is that Clojure libraries like tools.logging (mostly) hide this mess at the level of your code. The complexity is (mostly) constrained to picking a logging implementation.

Joe09:06:00

I have a structure like this

[{:name "fred" :food "burger"}
 {:name "fred" :food "pizza"}
 {:name "fred" :food "apple"}
 {:name "jane" :food "kebab"}
 {:name "jane" :food "sandwich"}]
And I want to turn it into a structure like this
{"fred" ["burger" "pizza" "apple"]
 "jane" ["kebab" "sandwich"]}
Is there way to achieve this without needing to reduce?
(reduce (fn [m {:keys [name food]}]
          (update m name conj food))
        {}
        [{:name "fred" :food "burger"}
         {:name "fred" :food "pizza"}
         {:name "fred" :food "apple"}
         {:name "jane" :food "kebab"}
         {:name "jane" :food "sandwich"}])
(the observations in the sequence are guaranteed unique, so was thinking maybe using some set functions?)

Joe10:06:58

Also, the solution above reverses the order of the observations in the results (giving {"fred" ("apple" "pizza" "burger"), "jane" ("sandwich" "kebab")}) is there a way to force conj nil thing to use a vector?

pyry10:06:40

conj will return different collection types depending on the type of collection you pass in it and for now it looks like conjing to nil will return a seq. If you want to be certain that you're getting a vector from your call to update, you could try instead something like (update m name #(conj (or % []) food) as the body of the reducing function.

👍 4
Bartimaeus10:06:45

Hello, can anyone help with https://github.com/stuartsierra/component ? The problem is that in this example: https://github.com/stuartsierra/component/blob/master/dev/examples.clj database connection is injected into get-user function when it's called inside ExampleComponent. But what if I want to call get-user function "outside" the component? For example, from another class in the runtime? Is it possible to get component instace (not create from the scratch) or something like that in the runtime and use it?

Lennart Buit10:06:10

Usually you would have another component, say your web service, that depends on the db connection component. So with component, you often don’t let this ‘state’ escape the system

Bartimaeus10:06:05

OK, but what if I want to create a library which would be a dependency for some app? I mean, use component in that library and call it's functions from the app. Then component isn't the appropriate solution? Because as I understand now, component should "cover" all app.

Lennart Buit10:06:27

You would create a library that takes its required state as arguments, and have your app manage that state with component

Lennart Buit10:06:28

So say you have some database function, (defn get-user [db id] ...)

Lennart Buit10:06:03

That your apps web stack would call with a db value it got from its db component dependency

Lennart Buit10:06:29

Does that clear it up for you ^^?

Bartimaeus10:06:31

OK, so in other words: component should be implemented in the app, not in the library?

Lennart Buit10:06:30

Generally, yes

Bartimaeus11:06:01

@UDF11HLKC OK, I have one more question if you don't mind. I have a task: pass some initial state and create a connection to some service on the app init. Save that connection somewhere. Later in the app runtime call functions and use that connection. What's the best way to do that? I don't know whether you are familiar with Erlang, but there would be a gen_server. What about in Clojure? Future / atom?

Bartimaeus11:06:17

In the other words: I need to do a stateful library. Init it on the app init and later call from the app.

Lennart Buit11:06:56

I’m not familiar with erlang, sadly. In component, you would create a component that manages this state for you, say ‘connection’, that you would inject with component/using in other components that need that state

Lennart Buit11:06:04

So your app, really is a component system that you started at some point

Bartimaeus11:06:31

But sadly I've already have an app and I think it would be a bit tricky to remake it with component. 😕

Lennart Buit11:06:27

Haha thats a different question! I would start by isolating global state from your functions. So I gave the example of this get-user function that took a db (or a connection ) as argument, right. In other word, this function is supplied its ‘global dependencies’

Lennart Buit11:06:44

instead of calling out to some global state itself

Lennart Buit11:06:45

That makes most of your application oblivious of global state.

Bartimaeus11:06:25

I understand, but I need a faster approach here. So maybe global state wouldn't be very bad for me.

Lennart Buit11:06:04

Thats for you to judge ^^. You can buy time by racking up technical debt, but you are paying interest 😉

👍 4
afleck13:06:19

in re-frame is doing something like (rf/dispatch [:set-foo (js/setInterval #(bar) 1000)]) considered 'impure'?

afleck13:06:52

just wondering what the best way to do something like this is

dpsutton13:06:33

clojurescript is strict so that is equivalent to (rf/dispatch [:set-foo 42]) or whatever number setInterval will return. there's a dedicated #re-frame channel to give more tailored advice. But you most likely would want to create an effect which would take a function and interval and it would schedule the interval rather than doing it there. but perhaps there's a better way and describing more of the problem could help

afleck13:06:23

@dpsutton that's exactly what I want to do, I want to keep track of the interval id so I can clear it later. I'm using that event to both start the interval and save the id for later clearing. but I will ask in #re-frame, thank you

nick15:06:59

add-watch is firing on each swap! Is it normal? I'm trying to capture "real" state updates - any changes to :artist or :title ;; "-- Atom Changed --" ;; "key" :watcher ;; "atom" #atom[{:artist "Rammstein", :title "RADIO"} 0x1a37dc61] ;; "old-state" {} ;; "new-state" {:artist "Rammstein", :title "RADIO"} ;; ;; "-- Atom Changed --" ;; "key" :watcher ;; "atom" #atom[{:artist "Rammstein", :title "RADIO"} 0x1a37dc61] ;; "old-state" {:artist "Rammstein", :title "RADIO"} ;; "new-state" {:artist "Rammstein", :title "RADIO"} ;; ;; "-- Atom Changed --" ;; "key" :watcher ;; "atom" #atom[{:artist "Rammstein", :title "RADIO"} 0x1a37dc61] ;; "old-state" {:artist "Rammstein", :title "RADIO"} ;; "new-state" {:artist "Rammstein", :title "RADIO"}

dpsutton15:06:15

that sounds bad

dpsutton15:06:21

it is not normal

dpsutton15:06:05

what is making you think add-watch is firing on each change versus the function you added as a watch firing on each change?

nick15:06:59

@dpsutton I don't know how to answer your question. Investigating.. add-watch is like this:

(add-watch song-atom :watcher (fn [key atom old-state new-state] (prn "-- Atom Changed --") (prn "key" key) (prn "atom" atom) (prn "old-state" old-state) (prn "new-state" new-state)))

Lennart Buit15:06:01

Tip: On slack you can do multiline code blocks by starting with triple backticks, then your code and then ending with triple backticks again 🙂!

🎉 4
nick15:06:03

@UDF11HLKC thanks. Will do next time

dpsutton15:06:24

what do you expect to happen?

dpsutton15:06:13

oh i see. you expect add-watch functions to only fire when the old and new vals are not equal rather than on each change to the atom's value, even if that value is the same?

nick15:06:28

yep, exactly

nick15:06:52

perhaps I should compare old & new state inside that watcher?

dpsutton15:06:03

an easy way to accomplish this is leverage old-state and new-state in your function. (when (not= old-state new-state) (prn new-state))

dpsutton15:06:15

you could also look at clojure.data/diff to find what is different if you like

nick15:06:31

got it. Thank you!

Phil Hunt18:06:56

Hi there. I'd like to start exploring Clojurescript now. I'm using Emacs / Cider to do Clojure and am fairly comfortable with that setup. What's a good starting point? Most of the resources I've googled look old and/or assume you know current cljs ecosystem fashions. What I'm hoping to find is the equivalent of the first few chapters of 'Clojure for the Brave' ie something that says, when Cider offers you 12 different REPL choices, pick this one.

Phil Hunt07:06:24

OK yeah, this looks like what I need. Doesn't collapse in a mess of dependencies. Makes me think I need to study up on modern JS ecosystem to get anywhere though. For example, I understand hiccup, but I'm now off to figure out what react-modal and NPX do.

Phil Hunt07:06:57

Looks like it might be very productive once I'm rolling though.

Phil Hunt07:06:47

So thanks for the really good resource !

dpsutton18:06:21

walks through how to make and deploy a shadow-cljs site with npm deps.

Phil Hunt18:06:33

OK cool thanks.

dpsutton18:06:38

once you get how shadow works it should seamlessly cider-jack-in-cljs and just work

Phil Hunt18:06:58

looks slick, thanks 🙂

amirali18:06:35

hi guys. I'm trying to write a nested loop recur but then it seems like its not working or perhaps it just goes on cause my repl doesnt do anything anymore... could u find my mistake or suggest a better alternative for nested loop-recur? Thanks

(defn test-recur []
  (loop [i 0]
    (if (> i 3)
      i
      (loop [j 0]
        (if (> j 3)
          (recur (inc i))
          (recur (inc j)))))))

seancorfield18:06:00

recur always goes to the closest enclosing loop so both arms of your if will recur to the j loop

seancorfield18:06:37

Your outer loop here is basically ignored @amirali.sadeghloo

🎯 4
amirali18:06:05

Thank you for your advice so how is it possible to write nested loop recur? if each time all recurs go for inner loop.

seancorfield18:06:59

Clojure's loop isn't like imperative language loops: it always returns a value.

seancorfield18:06:43

It's hard to give better advice without understanding what problem you are trying to solve -- there may be a much more idiomatic solution that doesn't involve loop at all.

amirali18:06:47

well Im trying to create a nested vector out of a normal one example: [0 1 2 3] to [[0 1][2 3]] so i thought nested loop recur would be nice

seancorfield18:06:33

(partition 2 [0 1 2 3])

seancorfield18:06:13

Clojure's solutions are often based on collections rather than looping.

🎯 3
amirali18:06:46

well that was easy... Thank u so much but then i would appreciate it if u were kind enough to give a nested loop example

seancorfield18:06:38

I can't think of an example that would try to nest loop...

seancorfield19:06:03

That's generally just not how you solve problems in Clojure.

seancorfield19:06:24

Some "nested loop" examples in other languages might map to a for expression in Clojure with multiple bound symbols: (for [i seq-a j seq-b] ...)

amirali19:06:48

oh so i was again thinking imperatively ... I do understand that clojure tries to solve problems using collections but then i still dont get it... HOW can i think in term of collections

seancorfield19:06:53

Again, for is an expression, not an imperative loop.

seancorfield19:06:21

Practice. Depending on your programming background, it might be harder or easier.

seancorfield19:06:49

Whenever you feel yourself reaching for loop, try to find an alternative approach based on a collection.

👍 4
amirali19:06:10

the reason i go for recur is that it creates collections rather than single values.

amirali19:06:24

should i study more abut sequence in general?

seancorfield19:06:12

Yes, the sequence abstraction is core to Clojure

amirali19:06:37

again Thanks for your advice 🙏

seancorfield19:06:39

I'm not sure what you mean about recur -- it really doesn't have any bearing on what is created.

seancorfield19:06:30

user=> (loop [n 0 s [0 1 2 3]]
  (if (seq s)
    (recur (+ n (first s)) (rest s))
    n))
6
user=>
loop/`recur` producing a value instead of a collection.

seancorfield19:06:02

(gotta go ... I'll be back in an hour or two I expect)

🖐️ 4
amirali19:06:48

yes u are right. i meant manipulating data in recursive way (As its immutable) 👍

okwori18:06:53

Hi guys, is there a way around running Clojure code within IntellIj IDEA's notebook?

seancorfield18:06:41

@simon There's a #cursive channel that might be better able to help...

Michael W19:06:17

(def m {:one 1 :two 2})
How do I get to this:
(:one {:value 1} :two {:value 2}}

seancorfield19:06:25

Do you really mean a sequence in that result, or do you want a hash map?

Michael W19:06:44

Supposed to be a map I must have typoed it

seancorfield19:06:09

(reduce-kv (fn [m k v] (assoc m k {:value v})) {} m) I think

Lennart Buit19:06:33

Yep, thats true, also in my repl 🙂. You beat me to the punch

Michael W19:06:40

Thanks, reduce-kv is nice and works perfectly.

Lennart Buit19:06:06

There is also medley, which is a small library of utility functions. It has a function called map-vals that would work perfectly here.

Lennart Buit19:06:10

That said, don’t go including libraries for single utility functions ^^

Lennart Buit19:06:34

… but I tend to spy in it if there is a function that does what I need

Michael W19:06:37

yeah I don' think I need all that but I read the api and there is some useful stuff in there

Michael W19:06:56

If I get more complicated than I am now I will be using that library

Lennart Buit19:06:35

Yep ^^. Just wanted to mention as it often has solutions for my common problems

David Pham20:06:25

I wonder if we will ever get a (map-vals f m) (for (reduce-kv #(assoc %1 %2 (f %3)) {} m) in clojure.core (obvisouly we have it in the library space).

andy.fingerhut22:06:16

Impossible to predict without reading Rich Hickey's mind, or bothering him by asking. It clearly hasn't been a high priority for the last 10 years.

andy.fingerhut22:06:08

I copy and paste a definition for it, or require the medley library, which has a good implementation, not in every Clojure project, but in some noticeable fraction. It's quick.

Michael W20:06:40

(def m {:one {:value 1} :two {:value 2}})
(reduce-kv (fn [m k v] (update m k #(assoc % :test "test"))) {} m)
;=> {:one {:test "test"}, :two {:test "test"}}
How do you append a key to a nested map? I tried assoc, update, assoc-in, update-in and they didn't add a key-value to the nested map the way I expected.

AC21:06:52

(reduce-kv (fn [m k v] (assoc m k (assoc v :test "test"))) {} m) ^ if i understand what you want (and you want to continue to use reduce-kv)

AC21:06:08

this would also work: (reduce #(assoc-in %1 [%2 :test] "test") m (keys m))

Michael W21:06:56

That's it exactly it's 2 assoc

adam21:06:29

Which is more idiomatic in Clojure: (if (= (get-in ctx [:request :request-method]) :get)) or (if (= :get (get-in ctx [:request :request-method]))?

seancorfield22:06:35

I don't know that one is more idiomatic than the other but I tend to put the constant/literal expression first, so that if I need to change it to match multiple versions I can just change it to a set:

8
🆗 4
seancorfield22:06:10

(if (= :foo (some-expression)) ...
(if (#{:foo bar} (some-expression)) ...

seancorfield22:06:14

and if I was filtering based on a value, I'd do the same order: (filter #(= :foo %) some-collection)

Michael W22:06:31

https://github.com/michaelwhitford/clojure-swapi/blob/master/src/swapi/core.clj#L93 I am getting a weird error trying to utilize the techniques we've discussed. I tried several ways to get it working, I have commented them out. Any help is appreciated.

seancorfield22:06:28

What error(s) are you getting @michael819?

Michael W23:06:46

Execution error (IllegalArgumentException) at swapi.core/add-schemas!$fn (core.clj:93).
Key must be integer

seancorfield23:06:56

And which of the four versions works and which gives that error? Or do they all give that error?

Michael W23:06:28

I got that error from another too but right now it's the uncommented one

seancorfield23:06:47

The error is basically saying that v in that assoc is a vector and :schema is not an integer...

seancorfield23:06:59

At least I think that's what it is saying.

seancorfield23:06:05

But what is r?

seancorfield23:06:24

I see :resources being added in a function above but what is it?

Michael W23:06:32

It's a a map from an atom

Michael W23:06:00

api! and add-api! functions add it

seancorfield23:06:17

[:url v] is a vector and that's what ends up in :resources and thus the v in line 93 is [:url "some value"] and you're expecting it to be a hash map instead.

seancorfield23:06:24

This would be a lot easier to debug if it was less imperative -- you have global mutable state and a top-level call to (add-api!) that is evaluated when you load the namespace which is not a good idea.

seancorfield23:06:47

Your functions are harder to reason about and harder to test because of the global mutable state.

seancorfield23:06:22

Are you writing tests for these functions, as you go?

Michael W23:06:45

no I haven't even looked at tests in clojure yet, just sitting at the repl and playing

seancorfield23:06:48

(I'm asking because I suspect if you were writing tests, you'd find they are hard to test because of the global mutable state)

seancorfield23:06:25

That's sort of a meta-issue, so back to line 31 -- should that be {:url v} there?

Michael W23:06:52

No, I was trying to append a new key to the nested map

seancorfield23:06:26

So you meant (assoc-in m [k :url] v) then?

Michael W23:06:13

I see where I got mixed up on assoc-in you have to specify the leaf in the vector.

Michael W23:06:58

In terms of state you are talking about the config atom?

seancorfield23:06:32

And then your functions are depending on it, rather than being passed it as an argument.

Michael W23:06:16

Thanks sean, I'll be on later and playing with it more. I really appreciate the feedback cuz I feel like I'm floundering when I should be soaring.