Fork me on GitHub
#beginners
<
2023-03-08
>
Δημήτριος Φκιαράς08:03:29

Hello respected Clojurians, is there a way to update the clojure inspector window showing a collection, without creating a new one each time?

Sam11:03:25

Hi, I want to try a new clojure project using https://clojars.org/defunkt/embodie I created a deps.edn:

{:deps
 {defunkt/embodie {:mvn/version "1.0.0"}}}
and a src/hello.clj :
(ns hello
  (:require [embodie.core :as core]))
And then tried to run the code with clj -X hello/run . However, it crashes with the error message
Execution error (FileNotFoundException) at hello/eval227$loading (hello.clj:1).
Could not locate embodie/core__init.class, embodie/core.clj or embodie/core.cljc on classpath.
Am I missing something obvious here?

elken11:03:51

https://github.com/mbuczko/embodie/issues/1 looks like you can't and the project seems dead

elken11:03:24

No more than a couple hundred lines total, just easier to pinch the bits you need

🚀 2
Sam11:03:41

Shame! Thanks, I will do some hardcore copy-pasting.

Jakub Šťastný12:03:21

Is there a way to destructure a nested map using :keys? I want to rewrite this:

((fn [{type :type {rate :rate} :meta}] [type rate]) {:type "test" :meta {:rate 0.1}})
; => ["test" 0.1]
... using :keys (which I'd prefer, in my actual code there's a lot going on and I'd like to keep it short). But I've been trying and I'm unsure whether it's even possible(?)

teodorlu13:03:25

something like this perhaps?

(let [x {:type "test" :meta {:rate 0.1}}
        {type :type} x
        {{rate :rate} :meta} x
        ]
    [type rate])
  ;; => ["test" 0.1]

teodorlu13:03:49

or:

(let [{type :type {:keys [rate]} :meta}
        {:type "test" :meta {:rate 0.1}}]
    [type rate])
  ;; => ["test" 0.1]

teodorlu13:03:12

or:

(let [{:keys [type] {:keys [rate]} :meta}
        {:type "test" :meta {:rate 0.1}}]
    [type rate])
  ;; => ["test" 0.1]

teodorlu13:03:23

does that make sense?

🙏 2
Jakub Šťastný13:03:14

So for a fn it's ((fn [{:keys [type] {:keys [rate]} :meta}] [type rate]) {:type "test" :meta {:rate 0.1}}) Great.

teodorlu13:03:21

Yup, that appears to work. Happy to help!

(let [m {:type "test" :meta {:rate 0.1}}
        f (fn [{:keys [type] {:keys [rate]} :meta}] [type rate])]
    (f m))
  ;; => ["test" 0.1]

🙏 2
skylize13:03:50

IMO, nested destructuring is quite difficult to read. Unless there is a strong need expose that detail in the function signature, I would lean toward choosing another way. Such as

((fn [m]
   [(:type m)
    (-> m :meta :rate)])
 {:type "test" :meta {:rate 0.1}})
; =>
["test" 0.1]

4
Jakub Šťastný13:03:23

Yeah I wonder what's the best place.

Jakub Šťastný13:03:01

What I actually have is this: {type :bizmentor.data/type {growth-frequency :bizmentor.data/growth-frequency} :bizmentor.data/metadata}

Jakub Šťastný13:03:33

And what I want is to get rid of the namespaced thing before prior to anything else.

Jakub Šťastný13:03:02

So that the fn body is always "clean".

skylize13:03:54

I would care more about keeping the function signature clean than the function body. The signature is an API, the body is just implementation detail.

2
Jakub Šťastný13:03:50

@U90R0EPHA what do you imply in this case?

skylize13:03:57

The function signature serves as a miniature API for anyone who wants to use your function. It shows up in tooling (e.g. when hovering the function name in Calva). The user can read the signature easily, and know what the API is, without needing to dig through the source. The body only gets read when you need to know the details of how things are implemented.

skylize13:03:59

So if you think that the showing all the things you are destructuring to the user of your function is important, then go ahead and use destructuring. But it is hard to read, so be sure that trade-off is worth it. If the goal is only to get a cleaner function body, then, in my opinion, it is definitely not worth it.

skylize13:03:41

One-level destructuring is usually pretty easy to read, so the equation is different. That is a good choice much more often than nested destructuring.

R.A. Porter14:03:28

When I have complex destructuring issues, I always refer to this excellent piece: https://danielgregoire.dev/posts/2021-06-13-code-observation-clojure-destructuring/

👍 2
R.A. Porter14:03:19

To follow on skylize’s point, if you want to combine a clean signature with documented and enforced nested params, consider a function spec.

teodorlu14:03:02

I also agree that the nested destructuring code I wrote above is hard to read. In my own code, I'd change

(let [{:keys [type] {:keys [rate]} :meta}
      {:type "test" :meta {:rate 0.1}}]
  [type rate])
to
(let [data {:type "test" :meta {:rate 0.1}}]
  [(:type data) (:rate (:meta data))])
. Though I support knowing the full reach of destructuring first, and then aiming for a simple solution!

👍 2
skylize14:03:20

> Though I support knowing the full reach of destructuring first, and then aiming for a simple solution! -@U3X7174KS You already pretty-well covered the "knowing" part by the time I found the thread. So I felt pretty comfortable diving straight into "Do you really want to do that?" 🙂

😊 2
Ryan15:03:17

I’m having trouble sorting a very basic thing: I deal with a lots of keyword vectors for mostly (get-in ) purposes.. a lot of these paths will have a common prefix, like [:app :data :people 12346] [:app :data :things 123456} Is there an easy and idiomatic way of extracting the shared prefix into a def for clarity?

teodorlu15:03:58

For a good, long answer to this, I'd recommend Zach Tellman's chapter on naming in his book Elements of Clojure: https://elementsofclojure.com/ It discusses this topic at length, and it's one of the best books i've read on programming. Though it's kind of hard, and kind of abstract, and won't give you quick, easy answer. --- For a short, specific answer, perhaps collect some helpers in a suitable namespace?

(appdata/people data 12346)
(appdata/things data 123456)

👍 2
teodorlu15:03:28

a bit more specific:

(let [some-data {:app {:data {:people {12346 {:name "Ryan"}}
                                :things {123456 {:make "Toyota" :color "Blue"}}}}}
        people (fn [data id] (get-in data [:app :data :people id]))
        things (fn [data id] (get-in data [:app :data :things id]))]
    [(people some-data 12346)
     (things some-data 123456)])
  ;; => [{:name "Ryan"} {:make "Toyota", :color "Blue"}]
(though I've lumped everything into a let rather than using namespaces -- so that it's easy to try out in a repl)

Ryan15:03:28

Thanks!

🙌 2
Ryan15:03:02

@U3X7174KS Or should I say, tusen takk!

teodorlu15:03:18

haha, bare hyggelig 🙂

tschady16:03:31

might want to check out #CFFTD7R6Z

👍 2
Nick19:03:38

Also, you may want to check out #C0FVDQLQ5 the video overviews on youtube of it will show you how to define paths to you data as named "selectors" , and makes it all very straightforward as well

oly15:03:20

This feels like it shoudl be simple using clj-http is there a way to capture specific exceptions ie any thrown via http-clients thrown-exceptions I am not sure I 100% get the example as its using slingshot, usually I would (catch Exception e) but that's any exception not specific to the ones thrown by clj-http https://github.com/dakrone/clj-http/blob/3.x/README.org#exceptions

daveliepmann17:03:22

It doesn't look like clj-http has any consistent data associated with their errors. I see a couple ex-info/`clojure.lang.ExceptionInfo` but also ProtocolException, Exception and so on in other parts of the code like handling multipart or redirects. On a brief skim it seems they're doing something interesting with the :type key in the ex-data — the example in the README has :clj-http.client/unexceptional-status, which you could check for — but it's not immediately obvious how widespread that pattern is.

oly08:03:15

Okay thanks not just me then missing something, the sligngshot example is a bit wierd it seems to destruct but not sure what its destructuring as it does not catch an exception, but I guess that's just me not being familiar with slingshot.

daveliepmann10:03:29

the destructuring in catch is a feature of Slingshot's try+ it's not just you, that example is surprising to my eye as well

daveliepmann10:03:26

I could be wrong on this point, but I'm pretty sure it is catching an exception?

(try+
  (client/get "")
  (catch [:status 403] {:keys [request-time headers body]}
  ...))
The vector after catch looks like what https://github.com/scgilardi/slingshot calls "a key-values vector", and the map after that vector is the exception which gets destructured.

👍 2
joakimen18:03:16

Working with an API that serves a json-structure with KV-pairs as :Key <keyname> :Value <value>. Is there any convenient builtin or approach to converting this into a “regular” map of kv-pairs? E.g. from {:Key "color" :Value "blue"} to {:color "blue"} The original structure looks like this

[{:id "i-081e920e2f8791de7",
  :tags
  [{:Key "Name", :Value "Bastion"}
   {:Key "StackName", :Value "cf-demo-stack"}]}
 {<another>}
 {<and another>}]

Grigory Shepelev18:03:43

((fn [{k :Key v :Value}]
         {(keyword k) v}) {:Key "Name" :Value "Grigory"})
;; => {:Name "Grigory"}

seancorfield18:03:52

Or if you want to get a bit fancy and use additional features from Clojure:

user=> (into {} (map (juxt :Key :Value)) [{:Key "Name", :Value "Bastion"}
   {:Key "StackName", :Value "cf-demo-stack"}])
{"Name" "Bastion", "StackName" "cf-demo-stack"}
build=> (into {} (map (juxt (comp keyword :Key) :Value)) [{:Key "Name", :Value "Bastion"}
   {:Key "StackName", :Value "cf-demo-stack"}])
{:Name "Bastion", :StackName "cf-demo-stack"}
build=>
This uses the transducer form of map and shows: a) how to "pour" pairs of values into a hash map b) how to create a pair of values using juxt to call two functions on the same argument.

clj 2
👍 2
daveliepmann18:03:24

Two approaches off the top of my head

(into {}
      (map (juxt :Key :Value))
      [{:Key "Name", :Value "Bastion"}
       {:Key "StackName", :Value "cf-demo-stack"}])
or
(let [xs [{:Key "Name", :Value "Bastion"}
          {:Key "StackName", :Value "cf-demo-stack"}]]
  (zipmap (map :Key xs)
          (map :Value xs)))
Choosing between these (or something else) depends on the context of how I'm encountering :tags while parsing this structure.

👍 2
daveliepmann18:03:28

I find Sean's (juxt (comp keyword :Key) :Value) very nice

teodorlu18:03:54

Here's a recursive variant, in case a :Value can contains other vectors that need to be transformed to normal maps:

(require 'clojure.walk)
  (clojure.walk/prewalk (fn [xs]
                          (if (and (sequential? xs)
                                   (every? map? xs)
                                   (every? #(contains? % :Key) xs)
                                   (every? #(contains? % :Value) xs))
                            (into {}
                                  (for [{:keys [Key Value]} xs]
                                    [(keyword Key) Value]))
                            xs))
                        [{:id "i-081e920e2f8791de7",
                          :tags
                          [{:Key "Name", :Value "Bastion"}
                           {:Key "StackName", :Value "cf-demo-stack"}
                           {:Key "Name", :Value [{:Key "FirstName" :Value "Grace"}
                                                 {:Key "LastName" :Value "Hopper"}]}]}])
  ;; =>
  [{:id "i-081e920e2f8791de7",
    :tags {:Name {:FirstName "Grace", :LastName "Hopper"},
           :StackName "cf-demo-stack"}}]
The "is this something we should transform?" check turned out a bit hairy -- perhaps it can be improved. If you don't need the recursion, I'd go with solutions suggested above!

joakimen11:03:42

This was way more help than expected, thanks a lot! (juxt (comp keyword :Key) :Value) seems like a great fit, probably gonna come in handy in the future too 😄

clojure-spin 4
Jakub Šťastný20:03:09

Is there a way to embed JSON directly into CLJ code? I do literate programming using Emacs Org mode and it'd be convenient to be able to embed last result using something like:

#+begin_src clojure :noweb yes
  (def results (some-read-json-macro
    <<projection-results>>))
#+end_src
The question can some-read-json-macro be defined? The JSON looks like this:
[{"year": 2026, "month": 2, "amount": 20}, ...]
My assumption is this wouldn't comply with Clojure syntax and so even a macro wouldn't be enough here to save the day. Or am I mistaken?

William LaFrance20:03:18

Is there an advantage to hardcoding JSON into your Clojure instead of transforming your data listing into EDN first so it can just be natively understood?

William LaFrance20:03:54

Even if a macro to do this is possible I think it’s more idiomatic to either use Edn for hardcoded data, or read and parse the JSON from a resource file.

pppaul20:03:54

if you are ok wrapping the JSON in a string, then you can use a reader macro to parse the JSON. #json "[{...}]"

💡 2
pppaul20:03:34

you have to write that macro, though, but could be 1-5 lines of code

Alex Miller (Clojure team)20:03:51

you'd then need to install that data reader - why not just (comment "[...]")

skylize20:03:21

Do you happen to be using ClojureScript? If so, would it work for your usecase to have a JS Object instead of JSON?

#js{:year 2026 :month 2 :amount 20 ...}

Jakub Šťastný20:03:00

I'm in CLJS actually, BUT the JSON comes from another tool.

Jakub Šťastný20:03:00

@U0LAJQLQ1 great link, thanks. I'm not sure I'm up for that at this very moment (new to CLJ and need to get that thing done, whichever way), but I'm definitely going to look into it later.

skylize21:03:03

> BUT the JSON comes from another tool. That shouldn't be a huge issue. Javascript is great at converting back and forth between JS Objects and JSON strings.

(js/JSON.stringify #js{:foo "bar"})
; =>
"{\"foo\":\"bar\"}"

(js/JSON.parse "{\"foo\": \"bar\"}")
; =>
#js {:foo "bar"}

Jakub Šťastný21:03:22

Escaping the quotes was one of the things I'd rather avoid. I'd have to do it through org-babel and I'm not sure whether there's a tool for that, so that it then embeds correctly as a valid CLJS code. Anyway I saved it into a different file for now and will convert to EDN.

skylize21:03:02

Well you don't need to add escapes to a string that already has the quotes in it. I'm not familiar with Emacs Org Mode, or 100% clear on what you are after. But I strongly suspect that if you just replace some-read-json-macro with js/JSON.parse, you will get the result you are asking for as a JS Object. And then to feed that data back into another function that expects JSON, you can first call js/JSON.stringify.

Jakub Šťastný21:03:22

Well it's basically embedding a file.

Jakub Šťastný21:03:28

So it's not escaped.

skylize21:03:01

\" is just how a quote char is represented in Clojure. If you slurp in a file and prn the resulting string, all the quotes in that string will be printed with the backslash in front of them as if they are escaped. But it's still just a quote char underneath.

skylize21:03:12

When you type a string literal, you have to type the backslash. In that case it is a functional escaping sequence. When it prints out with a backslash, that is just a visual representation.

Jakub Šťastný21:03:46

That's the thing, the noweb directive <<var>> simply puts whatever var contains (in my case output of a command that's a JSON format) in its place. So if I put that into a CLJ file, there's no slurping or anything.

Jakub Šťastný21:03:16

Anyway I already gave up on that and decided to write to file and read it from CLJ. Just wanted to save myself some work, but doesn't seem feasible.

skylize21:03:38

The output of your command "in JSON format" would be a string that is "valid JSON" and valid JSON will already have the quotes in the string. You don't need to jump through any hoops to escape those quotes. You only need to escape quotes when typing a string literal in your code.

Jakub Šťastný21:03:07

It does this: (def projections [{"year": 2023", ...}, ...])) It'd be valid JSON in a CLJ file, which would result in an invalid code, unless it could be read by a macro which would make it not eveluate. It wouldn't be 1 string. This is how the noweb replacement of <<var>>s works.

phill00:03:22

JSON embedded in code of any kind is a disappointment. Could the JSON be a distinct, pure-JSON exhibit in the Org file, which your Clojure code block would accept as an input or parameter?