This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2019-02-14
Channels
- # announcements (1)
- # beginners (206)
- # calva (2)
- # cider (64)
- # cljs-dev (12)
- # clojars (2)
- # clojure (177)
- # clojure-europe (2)
- # clojure-finland (1)
- # clojure-italy (2)
- # clojure-losangeles (5)
- # clojure-nl (7)
- # clojure-russia (69)
- # clojure-spec (41)
- # clojure-uk (92)
- # clojurescript (60)
- # core-async (16)
- # cursive (48)
- # data-science (6)
- # datomic (73)
- # duct (5)
- # events (2)
- # figwheel-main (5)
- # fulcro (29)
- # hoplon (1)
- # off-topic (52)
- # pathom (11)
- # reagent (4)
- # reitit (5)
- # remote-jobs (1)
- # rum (7)
- # shadow-cljs (58)
- # slack-help (10)
- # spacemacs (3)
- # testing (3)
- # tools-deps (5)
@conaw FWIT, I once wrote a tutorial about syntax quote http://blog.klipse.tech/clojure/2016/05/05/macro-tutorial-3.html
if anybody wants to have fun with instaparse, regular expressions, and property-based testing, doing a totally-achievable and well-defined thing, you could work on making this code as robust as the clj-jvm version: https://github.com/gfredericks/test.chuck/pull/54
it seems @U066U8JQJ is continuing that work here: https://github.com/wilkerlucio/test.chuck.sfr-cljs
I asked this yesterday afternoon but I don't think I did a good job of describing the question/issue, so I'mma try again but with actual examples this time. I have a macro that accepts an atom (and some other parameters), and binds a lot of helper variables for use in the macro:
(defmacro req [& expr]
`(fn ~['state 'side 'eid 'card 'targets]
(let ~['runner '(:runner @state)
'corp '(:corp @state)
'run '(:run @state)
'run-server '(when (:run @state)
(get-in @state (concat [:corp :servers] (:server (:run @state)))))
'servers '(zones->sorted-names (get-zones state))
etc... ]
~@expr)))
This macro is used a lot in the codebase, which means that all of these variables are being bound in every invocation. Most times, only a handful of the let-bound variables are actually used or needed in expr
. Is there some way to write the macro such that it will only bind a given variable if that variable is used in expr
?its anaphoric and you want to only create the binding if you can find a usage in the body?
I'm unfamiliar with "anaphoric" in this setting. What do you mean?
the macro creates a symbol for the body to use seemingly without a binding creating it
Isn't let
a binding?
yes but its created at macroexpansion time and therefore not visible in the code you actually write
Ah yes, that's correct
(req (run-server))
is an example. you create the symbol run-server that expr
can use without itself creating that binding.
You are correct, yes
i bet @bronsa has an easy way to do this with t.a.jvm. I'm thinking a naive version of walk your expr for the first form might work in practice but would miss things in let bindings, doseq bindings, etc. You don't care if someone shadows since you would create an extra unused binding in a few rare cases. I'm guessing you could run the analyzer over the form, find which things are used and intersect that with the few you have here
Interesting idea, thanks!
its also possible you'll do a lot of work, hit rough edge cases, and after some tedious coding, realize you don't get any measurable performance difference.
That's what I suspected, which is why I haven't worked very hard at finding a solution, lol
(let [ast (ana.jvm/analyze
'(let [x 3]
(+ x another-sym something-else)))]
(into #{}
(comp (filter (comp #{:var} :op))
(map :form))
(ast/nodes ast)))
(def another-sym 4)
(def something-else 5)
(let [ast (ana.jvm/analyze
'(let [x 3]
(+ x another-sym something-else)))]
(into #{}
(comp (filter (comp #{:var} :op))
(map :form))
(ast/nodes ast)))
;;-> #{another-sym something-else}
That's wild. Thank you!
so if i'm reading the AST stuff correctly, you could perhaps figure out how to collect all vars (http://clojure.github.io/tools.analyzer.jvm/spec/quickref.html#var) from the AST of expr and then find which ones are your bindings
and tools.analyzer has an ast namespace with a nodes
function to give a lazy sequence of nodes in the AST
there's also a really cool looking ast->eav function so you could do this in datalog.
if you want to go down this route, you'd need to collect local bindings that can't be resolved, there's a hook for that
I do agree with @dpsutton though that it sounds like it could be just premature optimization
> The analyze function can take an environment arg (when not provided it uses the default empty-env) which allows for more advanced usages, like injecting locals from an outer scope: this style?
what I was thinking of was to disable the part of the validation pass that throws an error on unresolved symbol names
(require '[clojure.tools.analyzer.jvm :as j])
(require '[clojure.tools.analyzer.ast :as ast])
(defn find-undefined-locals [expr]
(->> (j/analyze expr
(j/empty-env)
{:passes-opts (merge j/default-passes-opts
{:validate/unresolvable-symbol-handler
(fn [_ s {:keys [op] :as ast}]
(if (= op :maybe-class)
ast
(throw (Exception. ""))))})})
ast/nodes
(filter (fn [{:keys [op]}] (= op :maybe-class)))
(map :class)))
(find-undefined-locals '(let [a identity] (a b))) ;=> (b)
@dpsutton @nbthedukeThat's cool as hell, thank you
honestly, I haven't used/worked on t.a.jvm
in anger in years so I can't remember off the top of my head how to do it, just that it's possible :)
This is probably an unnecessary optimization (seeing as nearly every binding is either grabbing data from the map atom or performing minor calculations on the atom), but it's currently used 2344 times in the codebase, so it seemed worth at least peeking into
You could do something like
(defn seq-has? [form namespace-qualified-quoted-sym]
(when (seq? form)
(not
(empty?
(filter #(= % namespace-qualified-quoted-sym) form)))))
(defn expr-has? [expr namespace-qualified-quoted-sym]
(when (coll? expr)
(not
(empty?
(->> expr
(tree-seq coll? seq)
(filter #(seq-has? % namespace-qualified-quoted-sym)))))))
Adapted from some other code I have, so not tested...
(oops, didn't full adapt it... fixed)if you run this over (do a)
and (let [a 1] a)
it would tell you that both expressions are using the undefined symbol a
That makes sense. Won't play nice if somebody intends to (or accidentally) shadow the sym
Well, I suppose you could fancy it up to not catch on quoted or binding forms of the symbol. But yeah, add in all that complexity and you might as well reach for the analyzer
it doesn't indeed but the AST format now matches t.a
so you should be able to use the passes and the rest of the framework over the AST it produces
I haven't tried it but I think @U055XFK8V does that
you can use cljs.analyzer
, get back an AST and feed that AST to tools.analyzer
passes/traverse it using the t.a utilities
don't know how easy or feasable that is, but some work went into cljs last year to allow this
You just call expr-has?
in your macro. You may need to check for both the namespace qualified and non-namespace qualified version of the symbol. But that's extra macro stuff.
(require '[clojure.tools.analyzer.jvm :as j])
(require '[clojure.tools.analyzer.ast :as ast])
(defn find-undefined-locals [expr]
(->> (j/analyze expr
(j/empty-env)
{:passes-opts (merge j/default-passes-opts
{:validate/unresolvable-symbol-handler
(fn [_ s {:keys [op] :as ast}]
(if (= op :maybe-class)
ast
(throw (Exception. ""))))})})
ast/nodes
(filter (fn [{:keys [op]}] (= op :maybe-class)))
(map :class)))
(find-undefined-locals '(let [a identity] (a b))) ;=> (b)
@dpsutton @nbthedukein your example if you use (find-undefined-locals expr)
you should be able to find the set of locals that you need
With something like this, is it happening at compile time or run-time? heh cuz I suspect the overall performance cost of doing this at runtime will overshadow the performance gain from skipping the unused variables
this is assuming that you do something like (defmacro whatever [body] (let [neded-locals (find-undefined-locals body)] (emit-only needed-locals body)))
Where is emit-only
coming from?
that's what you write to create only the binding forms that are in the set needed-locals
it's the binding forms of your let form in the original macro, moved to a function to make it easier to test and the macro easier to reason about
Having messed around with this for a bit, I have a small question: how do you go about let
binding the needed-locals
? I can construct a vector of the necessary forms, but I'm having trouble using that vector in a let
as the bindings. Or is there some much easier solution to this?
Ah, okay, thank you, that was the part I was missing
Ah okay, thanks, that's cool
I'll mess around and hopefully make it work!
just added a slightly more performant version of find-undefined-locals
to https://github.com/Bronsa/tools.analyzer.jvm.deps/commit/8c7c3936e6f73e85f9e7cc122a2142c43d459c12
if you're gonna bump some stuff, the project.clj version is out of date with the pom. little confusing
I've never needed to have a caching in my Clojure program before, so I need some guidance.
I have a collection of entities (maps), that are calculated via some expensive functions using data-fetching from the database. Basically, I want to have it in cache as a big map with entity-id
as keys and corresponding hash-map as values. I looked up idiomatic ways to handle caching in Clojure, it's suggested to use clojure.core.cache
and atoms. It seems to me that none of eviction policies are suitable for my case, so I resorted to use BasicCache
. Additional to my "main" cache, implemented as an atom which contains plain entries, I want to have a nested lookup map with same entities grouped in keys for fast access. I want to store this derived lookup map in some atom for future uses, rather than calculating it every time (it's ~ over a million values).
I have two questions:
1) Should I use clojure.core.cache
's BasicCache
? It seems to me that it's just a wrapper over regular map, what benefits do I get using it?
2) Is it ok or idiomatic to use add-watch
as a way to keep in sync two atoms? So that my derived-lookup-map in second atom is always relevant and in sync whenever plain data in first atom changes?
@alisher.atantayev Not sure about BasicCache, but for 2, on the client a lot of people would just use re-frame subscriptions for that, which lets you achieve the same with more precision and less imperative code. Not sure if anyone uses that on the server, though
Yeah, I was looking for something like signals/subscriptions, but on the server-side. Was thinking watches would provide same functionality.
Keeping atoms in sync is a domain error - it's not what atoms are for. You can synchronize changes to refs to ensure that they stay coherent, but it's not something you can do generally / safely with atoms.
the advantage of BasicCache is that it uses the cache protocols, so you can swap in other caches with other behaviors without changing surrounding code
eg. you thought you needed a TTL cache, but now product says no more time outs, BasicCache is a simple change without rewriting surrounding code
or, you know you want a cache, but specific behaviors are going to be elaborated as needed, so you start with a BasicCache so you don't need to rewrite code to get the new caching behaviors in the final result
if a second atom is always tied to the state of a first atom, and isn't changed itself, you can replace it with a function on the first atom, or added fields in a map in one atom
I'd expect you'd want an annotation on a class, not a function per-se?
I'm trying to do this:
@Trace (dispatcher=true)
public void run() {
// background task
}
without needing to do some crazy definterface & deftype stuff.well, you need a class with a run method that's annotated, you can use gen-class and get a method without an interface, but the idiomatic thing in clojure is to use defprotocol (which defines an interface) then defrecord, deftype, or reify to get something implementing it
unless the method name doesn't actually matter, in that case functions have an invoke
method, but there's no way to annotate it
you could reify or otherwise implement clojure.lang.IFn, and that way get something that calls like a normal function and also has annotations
yeah, one moment, I have an example somewhere
Something like:
(deftype TracedFn [traced-params]
IFn
((with-meta 'invoke traced-params) [_]))
I don't think the deftype params are in scope. Even this doesn't work:
(deftype TracedFn [traced-params]
IFn
(^{com.newrelic.api.agent.Trace traced-params}
invoke [_]))
when you say it doesn't work, you mean because you can't have a different set of annotations for each instance?
Sorry, I should've been more specific. I get this:
Syntax error compiling at (impl.clj:19:1).
Unable to resolve symbol: traced-params in this context
even with java. I don't think you can have different annotations on methods each instance of a class
oh, yeah, it prevents capture in deftype
I'd just use reify unless you really need multiple instances (why? I don't make multiple instances of functions...)
otherwise, yes, a macro that fills it in, since lexical capture is unavailable in deftype / defrecord
Right, makes sense. Thinking the IFn path is the easiest. No need for an extra interface here.
be sure to define every applicable arity for invoke, plus applyTo
:thumbsup: (bad emoji autocomplete haha)
if you don't define applyTo, apply breaks
I'd rather take the time to define applyTo, rather than risk a really counterintuitive error if I ever refactor and use apply
you only need to define the arities you implement, of course
if you need numeric perf, the method overloads for primitives are there too...
(where fn gives you that "for free" if you satisfy the static constraints the compiler knows how to use)
pretty much, yeah, the multiple overloads is how you get good fast paths (at the expense of human radability)
BTW, another reason you need appyTo
: https://dev.clojure.org/jira/browse/CLJ-1715
Looks like that's how AFn does it: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/AFn.java#L147
nice to see the kind of bug I was avoiding by just superstitiosly using applyTo even though I didn't think I needed it :D
FWIW, ended with :
(defn- invoke-methods
[bodies meta-map]
(for [[args-list & body] bodies]
`(~(with-meta 'invoke meta-map) [~'_ ~@args-list] ~@body)))
(defn- make-traced2
[deftype-name traced-meta fn-name bodies]
`(deftype ~deftype-name []
IFn
~@(invoke-methods bodies (default-traced-meta traced-meta fn-name))
(~'applyTo [this# args#]
(AFn/applyToHelper this# args#))))
with a bit of wrangling you could turn that into fn-traced
or traced-fn
or whatever
I wish Clojure had some helper functions for parsing defn args... going to attempt to do that with spec
so you can't just delegate by passing in the argslist as is? or is it that deftype methods don't support destructuring etc?
Gotta take in all this stuff
'([name doc-string? attr-map? [params*] prepost-map? body]
[name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])
and spit out a defn matching what I was given.ahh, if you want 100% compatibility I can see that...
I'd be tempted to just support what I consider "normal" in defn
yeah - they made one for map-like things...
I've seen this: https://clojure.org/reference/datatypes#_java_annotation_support which sounds like the answer is no.
I am finding this: https://github.com/clojurebook/ClojureProgramming/blob/master/ch09-annotations/src/main/clojure/com/clojurebook/annotations/junit.clj
it looks pretty magical tho
What do people think is the oldest clojure version a library should support?
For a brand new library, Clojure 1.9.0 would seem reasonable.
The latest Clojure survey results show that 97% of respondents were using Clojure 1.8.0 and later: https://www.surveymonkey.com/results/SM-S9JVNXNQV/
Well that was a multi select question so careful with the number