is there a canonical way to do something like (.. some-obj (SpecificClass/.instanceMethod))?
I guess thread-first is good enough
why use .. at all?
Yeah, that's more or less the conclusion I came to
the answer originally was just clarity I guess
(SpecificClass/.instanceMethod some-obj) seems clearer
I do want to be clear the question is about chaining multiple such instance methods, which is why the .. in the first place, I thought that would be obvious since .. is useless when there's only one instance method being called, but it appears I was incorrect on that.
would still prefer -> as it subsumes all the functionality of ..
I think .. may also be confusing in that it is rewriting as . which is actually ignoring the class qualifier (there's some deep differences here in analysis)
Yeah, it definitely does ignore it, that's just not obvious without looking at the macroexpansion I don't think
even looking at the macroexpansion it's not obvious, b/c that happens in the . special form
when we added the qualified syntax we looked into using that part of the space but it would have broken a lot of existing code that assumes the qualifier is ignored (and it's regularly wrong)
> .. joins memfn in the graveyard
We used to have quite a few memfn uses but haven't for a while. We still have 16 uses of .. in 6 files tho'...
This is a question about late binding, function lookups, vars, etc. I have a program that has a namespace called stages, in it there is a function:
(ns stages)
(defn metadata-call [input]
(request input metadata-instr metadata-schema))
And in another namespace called pipeline I am using it, like this, and some core.async code:
(ns pipeline
(:require stages))
;; Define wrapper function
(defn metadata-call* [input]
(let [output (stages/metadata-call input)]
...))
(defn create-pipe [batch-size parallelism f]
(let [size 10
in (a/chan size (partition-all batch-size))
out (a/chan size (remove nil?))]
(a/pipeline-blocking parallelism out (map (try-wrap f)) in)
{:in in :out out}))
;; Using the wrapper function here.
(def metadata-pipe (create-pipe 10 10 metadata-call*))
And when I am testing the pipeline namespace, I am using with-redefs to redefine the stages/metadata-call function.
(deftest complete-pipeline-test
(with-redefs [stages/metadata-call metadata-mock]
...))
Now, what is very odd to me is that that this does not work. When I run the test, the original stages/metadata-call is called. I thought it would be dynamically looked up. Clojure is supposed to use late binding, right? And I am in a CIDER development REPL.
But if I change
(def metadata-pipe (create-pipe 10 10 metadata-call*))
To:
(def metadata-pipe (create-pipe 10 10 #'metadata-call*))
Then it does start working, meaning the mock is called instead. But this makes no sense to me, since it was not pipeline/metadata-call* that was redefined in the test, but stages/metadata-call. What does it matter if the pipeline/metadata-call* is looked up through a var if it doesn't change?something to keep in mind is with-redefs is mutating global state (the value of vars) in a way that can be a problem with multithreading
but that is not the issue you are running into here
the issue is you have a top level expression (def metadata-pipe (create-pipe 10 10 metadata-call*)) and top level expressions are evaluated at code load time (as if typed into a repl)
and function calls like (create-pipe 10 10 metadata-call*) are evaluated by all the parts, so create-pipe(symbol) evals to the create-pipe function, 10 resolves to 10 (both times) and then metadata-call*(symbol) evals to metadata-call* function, and then the function create-pipe is applied to those evaluated arguments
the way eval transforms symbols into values is it looks at the lexical environment for a local binding, if there is none it checks to see if the name resolves to a var, and then derefs the var
it is similar to
(let [f (fn [x] (fn [] x))
a (atom 0)
g (f @a)]
(reset! a 1)
[@a (g)])
you have a mutable reference to something, you take the value it is pointed at some point in time, then mutate the reference to another value, and wonder why the value you got earlier didn't change
#'metadata-call* works because you are passing the var object itself (like passing a pointer, an indirect reference), not the value that the symbol metadata-call* eval'ed to
and vars happen to be callable as functions, and when you call them as functions they forward the call to whatever they point to
Hm, but I don't change pipeline/metadata-call*.
> you have a mutable reference to something, you take the value it is pointed at some point in time, then mutate the reference to another value, and wonder why the value you got earlier didn't change
But the value I got when calling create-pipe was the function metadata-call*, and later, #'metadata-call* should contain the same function. I haven't changed it. Only the var stages/metadata-call has changed.
Is there something I am missing?
with-redefs is changing it
(def metadata-pipe (create-pipe 10 10 metadata-call*)) runs when the code is loaded so create-pipe is running with the value it got for metadata-call* at that time
But I thought it only changed the stages/metadata-call one. I mean, that's what it says.
(with-redefs [stages/metadata-call metadata-mock]unclear, then, you might be running into the multi-threading issue I mentioned
or actually slightly different
e.g. spinning up another thread (pipeline) inside a with-redefs, but the work on the other thread is happening after the with-redefs is done
hard to say without more information about the test
it really does sound like what are describing is the capture thing, and the code you shared obviously isn't your real code, so there could easily be some nuance etc that didn't get included in the example
for example if create-pipe immediately called f inside its body
No, I have run into that problem too, but that's not the issue now.
If I use #' then all the calls are using mocks, and if I don't, all the calls are using the original functions.
The test is:
(deftest complete-pipeline-test
(with-redefs [stages/lemmas-call lemma-mock
stages/split-call split-mock
stages/metadata-call metadata-mock
stages/conjugations-call conjugations-mock
stages/sentences-call sentences-mock
p/spit-ednl (fn [filename data]
(println (str "Spitting to FILE: " filename " DATA: " (vec data)))
(println))
slurp (fn [filename]
(println "slurping" filename)
(println)
(str/join "\n" (repeatedly 1000 rs)))]
(p/put-words-into-pipeline 100)
(let [all-results (a/
Meaning it starts by putting stuff in the beginning of the pipeline, waits for everything to complete and the end channel to close before the with-redefs end. I had a problen earlier where the with-redefs exited too early, and the last couple of calls was to the original functions.do you have direct linking turned on when running tests?
I don't think so. How do I know?
Dclojure.compiler.direct-linking=true
-Dclojure.compiler.direct-linking=true
I am running everything from a CIDER development REPL so I expected it to not be set.
I think cider just defers to however your project is setup
you should be able to get the system properties in the repl to see if it is set there
(System/getProperty "clojure.compiler.direct-linking") was nil
my money is still on the capturing previous value thing, the fact that switching to a var fixes it is pretty definitive
Hm but what I don't get is that the metadata-call* function has not changed, as far as I can see.
it is just a question of figuring out where and how that is happening, but don't have a nice succinct way to do that
depending on how complex your actual code is, any higher order (function passing) that is executed before the with-redefs usage could be it
Hmm, I will continue to try to figure it out. Thank you for your time!
Just had a related issue, I think. my-macro takes a string x and uses 'binding' to tie it to a dynamic var in that thread. Immediately after in a test, inside the body of the macro, I read the dynamic var, but x is nowhere to be found. #'my-dynamic-var solved the issue.