This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-04-29
Channels
- # asami (1)
- # babashka (20)
- # beginners (3)
- # biff (1)
- # calva (5)
- # clj-kondo (68)
- # clojure (6)
- # clojure-europe (70)
- # clojure-losangeles (4)
- # clojurescript (1)
- # cursive (6)
- # exercism (1)
- # fulcro (3)
- # hyperfiddle (23)
- # introduce-yourself (1)
- # kaocha (1)
- # lambdaisland (8)
- # lsp (6)
- # off-topic (24)
- # pathom (24)
- # specter (1)
- # tools-deps (4)
- # xtdb (4)
I'm weighing options for linting the two main macros used to define every operator in primitive-math (variadic-proxy and variadic-predicate-proxy). I like kondo a lot, but it seems like we have many options when it comes to macro linting, and no guide. I could use a flowchart 😉
I originally thought I could use the macros as their own hooks, but then I wondered if :lint-as
def
or declare
would be better, since I mostly just want the various operators to be resolved.
Linting as def
works, but using def
doesn't turn up an error if the operators are used as higher-order fns, which it should, since they're macros.
Thoughts?
One catch is, they're macro-generating macros.
Let's start with an example. What often only matters is how the code is used superficially, not what the macros generate in real life
Sure, here's varidic-proxy
:
(defmacro ^:private variadic-proxy
"Creates left-associative variadic forms for any operator."
([name fn]
`(variadic-proxy ~name ~fn ~(str "A primitive macro version of `" name "`")))
([name fn doc]
`(variadic-proxy ~name ~fn ~doc identity))
([name fn doc single-arg-form]
(let [x-sym (gensym "x")]
`(defmacro ~name
~doc
{:arglists '([~'x] [~'x ~'y] [~'x ~'y & ~'rest])}
([~x-sym]
~((eval single-arg-form) x-sym))
([x# y#]
(list '~fn x# y#))
([x# y# ~'& rest#]
(list* '~name (list '~name x# y#) rest#))))))
(I tried dropping it in as a :macroexpand
hook, and removeing the ^:private, but it doesn't seem to be working...)
So you want support for this macro for yourself, not for third party users, am I right?
Mostly for users, because it defines all the public operators in primitive-math
+, -, rem, >=, etc
I suppose I could do declare
, which would be friendly even to non-kondo users, but I wanted to see how ar I could get with hooks
or you could even just put declare
inside of the code itself, just to satisfy kondo, it doesn't hurt anyone
Right, but as I mention in the parent comment, that won't trigger an error if someone attempts to use them as a HOF
(Which is probably rare, but who knows)
And not definlined
I think clj-kondo currently doesn't warn when a macro is used as a hof, but that might come sometime soon. In that case you could start doing this probably:
(declare ^:macro + ^:macro - ...)
Ahh, interesting. And in that case, no hooks are needed, I assume
Heh, I decided to write a little declare-macro
macro, and immediately gave myself the same problem all over again.
Another way is to get rid of the macrology altogether and just copy paste the +
, -
etc macros. The low tech solution :)
I'm using a Lisp, dammit. I should be able to use macros everywhere!
More realistically, I'd prefer not to touch the core code of a library as old and widespread as primtive-math. It would probably be fine, but I don't really want to fool with it...
agreed: > the golden rule is to change nothing - unless we're adding something or fixing a bug (David Nolen, some years ago)
Always a good point.
Well, this is a fun one:
src/clj_commons/byte_streams/pushback_stream.clj:0:0: error: Can't parse src/clj_commons/byte_streams/pushback_stream.clj, java.lang.NullPointerException
Works in the REPL, btw
I think this has to do with configuration. E.g. this doesn't cause an NPE:
clj-kondo --config-dir /tmp --lint src/byte_streams/pushback_stream.clj
Interesting. Maybe some lingering reference in the imported primitive-math config.edn?
No, not that.
...
{:lint-as {byte-streams.utils/defprotocol+ clojure.core/defprotocol
byte-streams.utils/deftype+ clojure.core/deftype
byte-streams.utils/defrecord+ clojure.core/defrecord
byte-streams.utils/definterface+ clojure.core/definterface
clj-commons.byte-streams.utils/defprotocol+ clojure.core/defprotocol
clj-commons.byte-streams.utils/deftype+ clojure.core/deftype
clj-commons.byte-streams.utils/defrecord+ clojure.core/defrecord
clj-commons.byte-streams.utils/definterface+ clojure.core/definterface}}
CLJ_KONDO_DEV=true clj -M:clj-kondo/dev --lint src/byte_streams/pushback_stream.clj
Execution error (NullPointerException) at clj-kondo.impl.namespace/var-classfile (namespace.clj:112).
Cannot invoke "clojure.lang.Named.getName()" because "x" is null
Full report at:
/var/folders/j9/xmjlcym958b1fr0npsp9msvh0000gn/T/clojure-10659668507427392406.edn
Seems to be related to the older byte-streams.utils/deftype+
seems to error here: https://github.com/clj-kondo/clj-kondo/blob/a749864535f0bb6f227310acf84f2fa7081b2684/src/clj_kondo/impl/namespace.clj#L112
hmmm...what's really wrinkling my brain is the code is identical in the non-depreacated ns, but that's not causing problems...somehow
The code between the old and new namespaces is 99% the same
do the protocol+ macros support the exact same syntax as the original or are there slight differences?
Within byte-streams, the deftype+ macros are identical. Will have to check the usages
Between projects tho, Zach frequently changed those potemkin macros, so each one has a slightly different functionality, fwiw
Could it be related to the both
macro?
(Though that still wouldn't explain why it fails on the old ns, but not the new ns)
$ CLJ_KONDO_DEV=true clj -M:clj-kondo/dev --lint src/byte_streams/pushback_stream.clj
:ns byte-streams.pushback-stream :name PushbackStream
:ns byte-streams.pushback-stream :name Consumption
:ns byte-streams.pushback-stream :name ->Consumption
:ns byte-streams.pushback-stream :name trigger
:ns byte-streams.pushback-stream :name trigger
:ns byte-streams.pushback-stream :name put
:ns byte-streams.pushback-stream :name put
:ns byte-streams.pushback-stream :name expand-either
:ns byte-streams.pushback-stream :name expand-either
:ns byte-streams.pushback-stream :name walk
:ns byte-streams.pushback-stream :name walk
:ns byte-streams.pushback-stream :name prewalk
:ns byte-streams.pushback-stream :name prewalk
:ns byte-streams.pushback-stream :name both
:ns byte-streams.pushback-stream :name both
:ns byte-streams.pushback-stream :name nil
Execution error (NullPointerException) at clj-kondo.impl.namespace/var-classfile (namespace.clj:113).
Cannot invoke "clojure.lang.Named.getName()" because "x" is null
maybe it's this?
deftype+ (either [PushbackByteStream] [SynchronizedPushbackByteStream])
Yeah, but the identical code exists in clj-commons.byte-streams, too
Oh? Let me check that...
Huh, now I see it, too
I think we should make clj-kondo more robust, it doesn't handle the absence of a var name properly here, but hopefully you know what to do?
No, actually
Not yet. The both/either macros are convoluted, iirc
And they're only internal
well, I think either that, or just ignore the contents of both
and then declare
what you need to signal the existence of vars to clj-kondo, if they are important?
I've considered just separating the classes entirely for a while now. Or adding a property to indicate which is which.
Sometimes the fact that Hickey forbade inheritance is super-annoying.
Anyway, I know where the problm lays, anyway
Have a good day!