Fork me on GitHub
#clojure
<
2022-01-25
>
fadrian12:01:34

I've defined a macro, which seems to be correct at compile-time:

(defmacro expand [tag pnames err-check-fn err-check-msg fmt fmt-args]
  `{:tag ~tag :exp-fn (fn [ss rw]
                        (let [~pnames (:params rw)]
                          (if (not (apply ~err-check-fn rw))
                            [(error (apply ~err-check-msg rw))]
                            (apply (partial pp/cl-format nil ~fmt) ~fmt-args))))})
However, when I try to expand it, as so: (expand "Foo" [a b & c] (fn [x] (= 3 (count x))) (fn [x] (str "Bad " (:fn-name x))) "~a, ~a, and ~a" (a b c)) I get the following error:
Syntax error macroexpanding clojure.core/fn at (c:\Users\fadrian\Projects\mercator\src\main\clojure\expander.clj:15:1). (expander/ss expander/rw) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list expander/ss - failed: vector? at: [:fn-tail :arity-n :params] spec: :clojure.core.specs.alpha/param-list
It seems not to be liking the (:params rw) following the ~pnames. However, I'm passing in a well-formatted binding list for pnames and rw is defined as a parameter to the expansion function defined in the macro. If it's any help, I'm ultimately want to call the expansion function as follows:
(apply <exp-fn> nil {:tag "Foo" :fn-name "Bar" :params [1 2 3 4]}
Any ideas why I'm getting this error at macro-expansion time and how to get around it?

p-himik12:01:53

You didn't provide any error, but (a b c) in ~fmt-args will become just that - an unquoted list. It will be interpreted as a call of function a on arguments b and c.

fadrian13:01:40

I edited the message to include the error. I would assume that the apply in the last line would treat the list at the end position as a list or at least throw an error telling me that the result of (a b c) was undefined. I don't get that kind of error (probably because the code seems to be dying in the binding step before that).

fadrian13:01:27

Plus, a b and c should be defined by destructuring in the let.

fadrian13:01:06

I quoted (a b c) to ensure it wouldn't be evaluated and got the same message.

fadrian13:01:48

I've also taken out everything downstream of the binding and replaced it with a nil. I'm still getting the same error so it has to be something in the let expansion when I run the macro.

p-himik13:01:07

Ah, right. Use macroexpand-1 or macroexpand on your usage of (expand ...) and you should see that the argument vector in that inner (fn [ss rw] ...) will become something like [my.ns/ss my.ns/rw]. The syntax quote adds namespaces to all symbols. You gotta quote-unquote the symbols in the signature, like so: ~'ss.

fadrian14:01:21

I've simplified the function once again to:

(defmacro expand [tag pnames]
  `(fn [~'ss ~'rw]
                        (let [~pnames (:params ~'rw)]
                          (list a b c))))
However when I try to expand it using (expand "Foo" [a b & c]) it's telling me it can't find expander/a. I've tried removing the namespace for the a, b, and c references using ~', and when I do that the macroexpansion executes, but when I try to run it using (apply (expand "Foo" [a b & c]) nil {:params [1 2 3 4]}), the results are (nil nil nil) rather than the expected (1 2 (3 4)). For some reason it seems not to be seeing the bindings declared in the let or it's looking in the wrong namespace for them.

fadrian15:01:28

According to macroexpand, the macro expands to this: (fn* ([ss rw] (clojure.core/let [[a b & c] (:params rw)] (clojure.core/list a b c)))). So I don't see why the list isn't picking up the bindings.

p-himik15:01:13

Don't use apply when calling the resulting function.

fadrian15:01:10

OK. That works, but I don't understand why not to use apply on the function.

p-himik15:01:13

Such questions are more appropriate for #beginners In short, if you have (defn x [a b c] ...) then you either use (x 1 2 3) or (apply x [1 2 3]). The latter is only useful when you already have a collection - otherwise just use the former, without apply.

Nazral15:01:08

is there a way to turn a java object into a clojure map (say, at least all properties, and their subproperties etc...) e.g. if I have Foo that has property bar of type Bar which itself has a property baz of, then get something like {:Foo {:Bar {:baz :some-value}}} The goal is to easily explore the structure of a java object

potetm15:01:20

Have you looked at clojure.reflect/reflect ?

Alex Miller (Clojure team)15:01:04

there are two options - bean will use java bean properties to give you a map view

😮 2
Alex Miller (Clojure team)15:01:21

and ^^ for a fancier version (with two-way support)

markaddleman15:01:35

(jinx - you owe me a coke 🙂 )

Nazral15:01:39

Thanks a lot all ! That's great !

manutter5115:01:57

@UGH9KH1DF If you’re exploring, you might also be interested in clojure.inspector/inspect-tree.

Nazral15:01:13

thanks! that might be very useful too

Nom Nom Mousse15:01:16

Is there a better/shorter way to write:

(def m {"6bcefce3b9" :ready, "28b63dc81b" :not-ready}
(into {} (for [[k v] m] [k (if (= v :ready) :starting v)]))
Basically, return the map with one value switched.

Nundrum17:01:00

Is there some reason to not use update-in?

Yehonathan Sharvit12:01:28

update-in won't work: The condition is on the value not on the key.

💡 1
dpsutton16:01:11

That is the name of the game for this type of thing. Possibly nicer to extract some update-keys style function. But this strikes me as a “figure out how you are using and accessing you data and make a datastructure that makes that easy” type situation

🙏 1
mpenet16:01:14

yeah that^ . Otherwise you can use reduce-kv to at least stop iterating on values once you found/updated the one you want

mpenet16:01:37

reduce-kv + reduced with m as init and coll

mpenet16:01:01

not shorter, but slightly more efficient

Nom Nom Mousse16:01:25

I meant change all values of x to the value of y. Not one specific value. But I see what you mean. It was more a question of style than performance.

Nom Nom Mousse16:01:30

Of course, as the number of entries approaches some number my approach will become inneficient.

dpsutton16:01:08

Those look like ids and states. If you are working on the resources named by the id, you should have the set of them and then just update that set of keys so that their states are :starting. Perhaps you could also change your datashape to be {:ready #{ids ...} :not-ready #{ids ...}} and then you (start-resources (:ready m)), have that return the ids that successfully started and those that failed, and then update your m map accordingly?

Nom Nom Mousse16:01:23

Thanks for the suggestion. That makes other lookups more time-consuming though ("find the state of job x"): I'd need to search each set

richiardiandrea18:01:19

Hi folks, I googled this a bit, but I am not sure about it, basically can I (comp identity ...) and pass that as xform to (into [] ...)? Is identity a valid transducer?

lilactown18:01:06

no, it's not whatever ghadi says

😆 5
ghadi18:01:12

yes, it is

ghadi18:01:21

(into [] identity coll)

ghadi18:01:41

it is a transducer that doesn't change the reducing/step function

ghadi18:01:53

(which in into's case, is conj)

ghadi18:01:21

there's some backscroll in this slack about it @richiardiandrea

ghadi18:01:12

but you'd never need to comp identity with anything

richiardiandrea18:01:51

well I have a function that accepts an optional xform so I was basically wondering if I could default to identity

richiardiandrea18:01:41

so it will have to be comp-ed with a transducer (which is the initial transformation I have to do on top of the optional xform in input)

Jacob Rosenzweig18:01:00

Any conventions you all follow for naming partially applied functions?

lilactown18:01:11

I typically use anonymous functions for partial applications

Jacob Rosenzweig18:01:47

Funnily enough, I'm looking at a clojure style guide right now that prefers partial over anonymous fns. I guess the fn itself doesn't have to be partial, so I don't have to specify it as parital in my function sig

lilactown18:01:15

seems like a weird style guide. partial is less useful since it only works for fns that you are apply from the left

👍 1
1
lread21:01:16

You mean https://guide.clojure.style/#partial? Yeah I’m no particularly partial to partial either. Maybe that part of the style guide could use a tweak. It’s a https://github.com/bbatsov/clojure-style-guide/issues.

winsome21:01:55

Is there a document of what constitutes a valid keyword?

winsome21:01:04

I'm parsing a json map and want to know if I can keywordize the keys safely

hiredman21:01:20

depends what you mean by safely

hiredman21:01:15

the keyword function will allow you to turn any string into a keyword, and they will all work fine like any other keyword, except when it comes to printing and reading

hiredman21:01:00

it is super common to keywordize keys when decoding json, but I prefer not to

hiredman21:01:29

clojure.data.json and cheshire both have options for doing it or not (I think they have different defaults)

hiredman21:01:42

those are two common clojure libraries for json

winsome21:01:24

Yeah, I'm using cheshire. I want to avoid keywords that won't be readable/printable, so I'd like to check what's allowed. I vaguely remembered that the keyword? function is much broader in what it accepts than the reader

hiredman21:01:45

keyword? is just checking is something is a keyword

hiredman21:01:47

user=> (keyword (String. (doto (byte-array 5) (#(.nextBytes (java.util.Random.) %)))))
:f��5�
user=> (keyword (String. (doto (byte-array 5) (#(.nextBytes (java.util.Random.) %)))))
:@
user=> (keyword (String. (doto (byte-array 5) (#(.nextBytes (java.util.Random.) %)))))
:��I
user=> (keyword (String. (doto (byte-array 5) (#(.nextBytes (java.util.Random.) %)))))
:V���G
user=> (keyword (String. (doto (byte-array 5) (#(.nextBytes (java.util.Random.) %)))))
:+�o�
user=>

hiredman21:01:56

the keyword function well let you make a keyword object out of any string, it just won't print in a way that is guaranteed to print in way that is readable, or round trip through the reader

hiredman21:01:44

the reader docs https://clojure.org/reference/reader#_literals talk about what is allowed for keywords, but in practice the reader is even more relaxed about it

ghadi21:01:51

I would avoid blind keywordizing on untrusted user JSON

3
ghadi21:01:31

(keywords are remembered in a concurrent hashmap; you might open yourself up to certain denial of service attacks.)

ghadi21:01:02

(but with WeakReferences, so the impact might be blunted)