Fork me on GitHub
#clojure
<
2023-07-08
>
igrishaev09:07:57

I'm working with nested HashMaps as follows:

(def m (doto (new java.util.HashMap)
         (.put :foo (new java.util.HashMap))
         (.put :bar (new java.util.HashMap))))

{:foo {}, :bar {}}
Now I want to add a certain key into the nested :foo map. The code below does the job but triggers a reflection warning:
(-> ^java.util.Map m (.get :foo) (.put :a 1))

m
{:foo {:a 1}, :bar {}}

;; call to method put on java.lang.Object can't be resolved (no such method).
How can I solved it? I tried the double-dot macro, with-meta but none of these worked. Thanks!

oyakushev09:07:15

This works:

(-> ^java.util.Map m
    ^java.util.Map (.get :foo)
    (.put :a 1))

👍 2
delaguardo09:07:22

(let [^java.util.Map inner (.get m :foo)] (.put inner :a 1))

igrishaev09:07:09

Right, and I was trying

(.. ^java.util.Map m ^java.util.Map (get :foo) (put :a 1))
the code with -> works great

roklenarcic12:07:21

I’d write a utility function jmap-get-in

roklenarcic12:07:42

(defn ^java.util.Map get-submap [^java.util.Map m ks]
  (loop [^java.util.Map ret m
         [k & more] ks]
    (if k
      (recur (.get ret k) more)
      ret)))

roklenarcic12:07:25

so this makes it possible to return a nested map already marked with the right typehint

roklenarcic12:07:07

obviously this code is super sloppy: nil keys won’t work, and it doesn’t check if map has the key and that the actual return is a map

roklenarcic12:07:36

when using clj tools with -T is it possible to get function descriptions/ useage string?

roklenarcic12:07:19

roklenarcic@Roks-MacBook-Pro codescene % clj -Ttools show :tool new
{:lib io.github.seancorfield/deps-new,
 :coord
 {:git/tag "v0.4.13",
  :git/sha "879c4eb50df92fcd8c11423c2081e00998d7b613",
  :git/url ""}}
Default namespace:  org.corfield.new
this doesn’t show much which is a shame, because the functions in default namespace have nice docstrings on them

seancorfield18:07:46

I'm not at a computer to test this but something like clojure -A:deps -Tnew help/doc maybe?

seancorfield00:07:13

@U66G3SGP5 Now I'm at a computer, I can confirm that does indeed work.

roklenarcic08:07:36

What’s the purpose of -A:deps in that command?

seancorfield18:07:21

It adds the built-in :deps alias which is what makes help/doc available.

seancorfield18:07:34

The :deps alias is normally used with -X for the core tooling around pom files and dependency trees etc.

ray18:07:34

🧵 In JS when one has a symbol x defined as 5 one can use this syntax { x } and you will be able to access the result as a map { "x" 5 } which seems neat. So I tried to come with a macro to do something similar but I'm having some issues so am hoping that someone could help a novice macromancer (that's me and I hope it's not offensive to call myself that 😇 )

ray18:07:48

this is the code I have so far

ray18:07:11

(defmacro syms->m
  [syms]
  `(do (into {} (map ~(fn [k v]
                        [(keyword k) v])
                     '~syms [~@syms]))))

ray18:07:38

and it works ok this way

ray18:07:44

(let [x 1 y 2]
  (syms->m [x y]))
=> {:x 1, :y 2}

ray18:07:38

but calling it from a function is kinda brittle / weird

ray18:07:54

(defn use-syms->m
  [x]
  (syms->m [x]))

ray18:07:32

(let [x 1]
  (use-syms->m x))
=> {:x 1}

ray18:07:38

so far so good

ray18:07:01

not so nice...

ray18:07:09

(let [x 1 y 2]
  (use-syms->m [x y]))
=> {:x [1 2]}

ray18:07:02

I'm thinking it's do with the way the calls are either [x y] or [[ x y ]] but, assuming that's right, I'm not sure where to apply the guardrails?

ray18:07:18

Any tips / advice / edits would be welcome

Ben Sless18:07:20

I like this version

(defmacro locals [& xs]
  (zipmap (map keyword xs) xs))

(let [x 1 y 2]
  (locals x y))

👍 2
ray18:07:17

haha, yeah me too .... it's a ton less complex than mine 🙂

Ben Sless18:07:56

It had years bouncing back and forth in this slack to refine itself 🙂

😆 2
ray18:07:45

haha - thanks for not making me feel too dumb 😅

🙏 2
Ben Sless18:07:31

This is the seventh time we've had this thread, we've become quite good at it

😂 14
ray18:07:52

btw I'm still hitting the same call semantics issues so wondering if I'm actually dumb

ray18:07:10

(defn use-locals-map
  [& {:keys [x y] :as opts}]
  (locals x y))

(defn use-locals-list
  [& xs]
  (locals xs))

ray18:07:25

(let [x 1 y 2]
  (use-locals-map {:x x :y y}))
=> {:x 1, :y 2}
(let [x 1 y 2]
  (use-locals-list x y))
=> {:xs (1 2)}

Ben Sless18:07:39

runtime vs. compile time

Ben Sless18:07:04

you want it to work for both?

ray18:07:23

yes, indeed ... but Neo .... it works for the map

Ben Sless18:07:18

but how would use-locals-list pass the names of the arguments to locals?

Ben Sless18:07:52

I don't think it can work for that, call by value or call by need languages won't support it

Ben Sless18:07:07

you need F Expressions for that (you don't want F expressions)

ray18:07:15

yeah - I think that's where I've been taking the bullets and you've dodged them

ray18:07:51

ok - I get it ... 🙏:skin-tone-3:

Ben Sless18:07:12

The secret of dodging bullets is not being there to begin with 🙃

ray18:07:32

indeed, first rule of macro club

ray18:07:43

thanks Ben

didibus18:07:03

use-locals-list is passed 1 2 as argument

ray18:07:30

yes :star-struck:

didibus18:07:49

But why would you wrap locals in another function that does nothing more?

ray18:07:24

I was just playing ... 👶:skin-tone-3:

didibus18:07:32

It should work if you stay in macro-land

didibus18:07:47

If use-locals-list is itself a macro

ray18:07:01

my actual goal was to see if I could use it in a reader tag

oyakushev18:07:08

I think you could because reader tags are run at compile time. Even though the implementations are functions, those functions are producing code, not evaluating stuff.

didibus18:07:10

Hum, my intuition says you should be able too

oyakushev18:07:23

@U04V5V0V4

(defn locals-reader [v]
  (zipmap (map keyword v) v))

(binding [*data-readers* {'locals #'locals-reader}]
  (eval
   (read-string "(let [x 1, y 2]
                   (println #locals[x y]))")))


;; {:x 1, :y 2}

🎉 4
oyakushev18:07:43

Read/eval voodoo is only there to demonstrate in the REPL that it works. For actual usage, you add your reader function to *data-readers* before any namespace using that reader tag is evaluated, and it should work.

ray18:07:57

nice! I'll take the hit 🎯

didibus18:07:20

Data Readers if I remember correctly are actually read time. So they run before macros.

🧠 2
oyakushev18:07:34

Yes, my bad. It is why I had to use read-string in the example, I couldn't pass the quoted form to eval because it failed right as the form was read.

ray18:07:25

yes - if I use a function rather than a macro the reader function works from data_readers.cljas long as it's required as you say @U06PNK4HG

😎 2
oyakushev18:07:57

He's beginning to believe!

rich2 2
ray18:07:33

I'm not sure whether

(let [x 1 y 2] 
  #s->m [x y])
is really any better than
(let [x 1 y 2] 
  (s->m [x y]))
and the latter ensures that the function / macro is required

oyakushev18:07:06

Yeah, it's pretty minor. The main benefit from reader tags is that you lose a set of wrapping parens; unless you use the same tag really often, it's not a big deal to justify the complexity.

2
oyakushev18:07:14

You already know what I’m going to tell you.

ray18:07:50

:melting_face:

didibus19:07:22

You don't even lose a set of wrapping parens if you go with & xs though. So ya, don't think here the reader tag is really useful. In fact, in general, this is why reader tags aren't used much. It doesn't save you a lot of typing. Also the non-namespaced ones are supposed to be reserved to Clojure (in that they could add a core one that clashes with yours otherwise). And if you add a namespace to the tag, it's even longer to type

💯 2
didibus19:07:50

Where reader tags are good, is for what it's called: "reading". So if you defined a new data-structure, that you want to also then serialize into EDN and back.

ray19:07:43

yeah - I've used them for crypto stuff which is complex to reconstruct so doesn't really seem worth the bother here, it was fun hour in the sandpit with you lads though thanks3

ray19:07:57

oh and for completeness complex tags need a print-method defined - the fact that it's not needed here is another sign that it's not worth it

Noah Bogart19:07:30

In case y’all haven’t seen it: https://github.com/Chouser/spread

😍 2
didibus19:07:29

I like that, ya, if you don't need to extend print so i prints back as a literal tag, it's probably a bad use of reader tags.

oyakushev20:07:00

I had a valid use for reader tags once, for a case where they had to be used a lot (think 20-30 per file) and represented a thing with a distinctive domain meaning and identity. What also helped is that they looked like "syntax" instead of regular code, and it was easier for the users (domain-aware programmers) to understand and remember that only specific things could be written inside the tagged form, not arbitrary code.

ray22:07:50

That’s an interesting case

ray05:07:03

More code so what’s the upside?

valerauko06:07:48

It works more like the JS literal. You can mix symbols and not-symbols, so you can write something like

(let [a 1
      b 2]
  (... a b :c 3))

2
geraldodev21:07:59

Why does the Cognitect test runner not have a throws? feature?

hiredman21:07:13

Test runner just runs tests, tests themselves are defined using clojure.test (or anything compatible) and clojure.test very much does have a thrown? thing, but it only works with the clojure.test assertion system, it isn't a standalone function/macro https://github.com/clojure/clojure/blob/4b9eadccce2aaf97e64bcc8e35c05c529df8fdd2/src/clj/clojure/test.clj#L504

❤️ 2
geraldodev22:07:46

Interesting, I've pasted code from doc into clj repl. I was expecting it to swallow the exception, but that is because I'm not in runner context...

user=> (require '[clojure.test :refer [deftest is testing ]] )
nil
user=> (is (thrown? ArithmeticException (/ 1 0)))
#error {
 :cause "Divide by zero"
 :via
 [{:type java.lang.ArithmeticException
   :message "Divide by zero"
   :at [clojure.lang.Numbers divide "Numbers.java" 190]}]
 :trace
 [[clojure.lang.Numbers divide "Numbers.java" 190]
  [clojure.lang.Numbers divide "Numbers.java" 3911]

hiredman22:07:24

It is being swallowed

hiredman22:07:01

What you are seeing is the exception being returned as the value of assertion, not being thrown

☝️ 2