Clojure tools.analyzer, don't expand macros - dynamic

What is the best way to avoid expanding macros when creating ast's using tools.analyzer.jvm/analyze
This is an example of the information I am currently collecting:
(map of function name to dependency set)
{some-function
#{{:name load-order-ns-file-maps, :ns #namespace[clj-graph.core]}
{:name *logger-factory*, :ns #namespace[clojure.tools.logging]}
{:name analyze, :ns #namespace[clojure.tools.analyzer.jvm]}
{:name make-dir-tracker, :ns #namespace[clj-graph.core]}
{:name enabled?, :ns #namespace[clojure.tools.logging.impl]}
{:name read-all, :ns #namespace[clj-graph.core]}
{:name get-logger, :ns #namespace[clojure.tools.logging.impl]}
{:name traverse-expr, :ns #namespace[clj-graph.core]}
{:name log*, :ns #namespace[clojure.tools.logging]}
{:name track-reload,
:ns #namespace[clojure.tools.namespace.reload]}
{:name examine-form, :ns #namespace[clj-graph.core]}}}
The actual code calls log/info but as this is macroexanded, I can never capture the the name and ns in which it was declared - instead I get the macro expansion giving me:
:name *logger-factory*, :ns #namespace[clojure.tools.logging]
:name enabled?, :ns #namespace[clojure.tools.logging.impl]
:name get-logger, :ns #namespace[clojure.tools.logging.impl]
:name log*, :ns #namespace[clojure.tools.logging]
As I am building a dependency graph, ideally I just want to find the endpoints, ie :name info :ns #namespace[clojure.tools.logging].
Reading the documentation for analyzer.jvm/analyze it gives an example as:
(analyze form env {:bindings {#'ana/macroexpand-1 my-mexpand-1}})
but when I try this, ie:
(defn ^:dynamic my-expand-1 [form] form)
(ana/analyze
'(defn prnt [xs] (my-pre-defined-macro xs))
(ana/empty-env)
{:bindings {#'ana/macroexpand-1 my-expand-1}})
I get the error
IllegalStateException Can't dynamically bind non-dynamic var:
clojure.tools.analyzer.jvm/macroexpand-1 clojure.lang.Var.pushThreadBindings (Var.java:320)

If macros were not expanded, you couldn't get any useful information, because what a macro expands into is completely opaque to you. Perhaps someone has written this function:
(defn foo [x]
(-> x
inc
println))
If you don't expand the -> macro, you will not find out that foo depends on inc and println. And finding out that it depends on -> is not very interesting.

I solved this for specific macros by providing implementations of macroexpand-1 and parse. My macroexpand-1 implementation ignores the specific macros I want to parse myself (by simply returning the form) and my parse implementation provides custom parsing. You can pass custom method bindings in the options to analyze:
For example, to provide custom parsing of the binding macro:
(require '[clojure.tools.analyzer.jvm :as ja])
(require '[clojure.tools.analyzer :as ana])
(defn my-parse [[op & _ :as form] env]
(if (= op 'binding)
(merge (ana/parse-let* form env)
{:op :unexpanded-dynamic-binding})
(ja/parse form env)))
(defn binding-form? [o] (and (seq? o) (= (first o) 'binding)))
(defn my-macroexpand-1
([form] (my-macroexpand-1 form (ja/empty-env)))
([form env]
(if (binding-form? form)
form
(ja/macroexpand-1 form env))))
(ja/analyze
'(binding [*clojure-version* 1] *clojure-version*)
(ja/empty-env)
{:bindings {#'ana/parse my-parse
#'ana/macroexpand-1 my-macroexpand-1}})

Related

Reagent performance issue when passing atom as a function parameter

I work on the react-native application using Clojurescript re-frame and reagent. I have one text input component, and have two versions of the code:
Version 1: input text is a separate component, and state atom is passed as an argument, the same as the recommended in the reagent library docs and examples.
(defn todo-input [value]
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
)
(defn todo-screen []
(let [value (r/atom nil)]
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[todo-input value]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]
[todos]
]))
Version 2: everything in one component.
(defn todo-screen []
(let [value (r/atom nil)]
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]
]]
[todos]]))
The issue is that first version has a performance issue while typing, since there's a big delay and flickering when trying to type fast. Version 2 doesn't have any issues, and I can type as fast as I can.
According to the reagent documentation, passing r/atom as a parameter should not incur any performance issues.
Am I doing something wrong here? What would be the best way to avoid performance penalty here.
This is a small example, and having one component instead of two is not a big deal, but splitting one big to multiple smaller components in a good praxis. State here is local to the component, and I don't want to use re-frame for it.
re-frame/dispatch puts your events in a queue for re-frame to process, so there can be a slightly delay before it actually goes through and your change will be there.
Sounds like you're experiencing the same issue stated here: https://github.com/day8/re-frame/issues/368
So one work around is to use re-frame.core/dispatch-sync which forces re-frame to handle the event directly and synchronously. You might have to also add a call to reagent.core/flush to force re-render the component. I haven't needed flush before when building web clients, but React Native seems to work differently.
You can read more about these two functions here:
https://github.com/day8/re-frame/blob/master/src/re_frame/router.cljc#L251-L263
https://github.com/reagent-project/reagent/blob/master/src/reagent/impl/batching.cljs
Mentioned in the issue above is also https://github.com/Day8/re-com that supposedly works around the issue somehow, but I didn't take a closer look at that.
Your solution #2 is not wrong either, it just gives you a different way of working. So if you need the data in your app-db to update on every keypress for example, only something more like #1 will work. Or using solution #2 but passing in the atom as an argument to your component.
Both of your versions have an issue. You should use Form-2 type components when using local state. Like this:
(defn todo-screen []
(let [value (r/atom nil)]
(fn []
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[rn/view {:style (styles :textInputContainer)}
[todo-input value]
[rn/touchable-opacity
{:on-press (fn [] (rf/dispatch [:add-todo #value]) (reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]
[todos]])))
More info about Form-2 here.
Or you could use the r/with-let instead. More info about with-let.
Regarding your original question, you could have a compromise between both your versions, and extract the input and submit button into a separate component:
(defn todo-input-container [on-press]
(r/with-let [value (r/atom nil)]
[rn/view {:style (styles :textInputContainer)}
[rn/text-input
{:style (styles :textInput) :multiline true
:placeholder "What do you want to do today?" :placeholder-text-color "#abbabb"
:value #value
:on-change-text #(reset! value %)}]
[rn/touchable-opacity
{:on-press (fn []
(on-press #value)
(reset! value nil))}
[icon {:name "plus" :size 30 :color "blue" :style {:margin-left 15}}]]]))
(defn todo-screen []
[rn/view {:style (styles :container)}
[rn/text {:style (styles :header)} "Todo List"]
[todo-input-container (fn [value] (rf/dispatch [:add-todo value]))]
[todos]])
Looks like the issue here is that reagent doesn't support well components with the controlled inputs due to its async nature.
Controlled input (via :value) should be avoided, or worked around by forcing component update immediately after changing :value.
See reagent issue and explanation for more details.

Test pre-requisites for tabular tests; how does tabular work?

Let's say I am attempting to test an api that is supposed to handle presence or absence of certain object fields.
Let's say I have tests like so:
(def without-foo
{:bar "17"})
(def base-request
{:foo "12"
:bar "17"})
(def without-bar
{:foo "12"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(def response-without-bar
{:foo "12"
:bar ""
:name "Bob"})
(def response-without-foo
{:bar "17"
:foo ""
:name "Bob"})
(facts "blah"
(against-background [(external-api-call anything) => {:name => "Bob"})
(fact "base"
(method-under-test base-request) => response)
(fact "without-foo"
(method-under-test without-foo) => response-without-foo)
(fact "without-bar"
(method-under-test without-bar) => response-without-bar))
This works as you would expect and the tests pass. Now I am attempting to refactor this using tabular like so:
(def request
{:foo "12"
:bar "17"})
(def response
{:foo "12"
:bar "17"
:name "Bob"})
(tabular
(against-background [(external-api-call anything) => {:name "Bob"})]
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which results in:
FAIL at (test.clj:123)
Midje could not understand something you wrote:
It looks like the table has no headings, or perhaps you
tried to use a non-literal string for the doc-string?
Ultimately I ended up with:
(tabular
(fact
(method-under-test (merge request ?diff) => (merge response ?rdiff) (provided(external-api-call anything) => {:name "Bob"}))
?diff ?rdiff ?description
{:foo nil} {:foo ""} "without foo"
{} {} "base case"
{:bar nil} {bar ""} "without bar")
Which passes. My question is. How does the tabular function differ from the facts function, and why does one of them accept an against-background while the other blows up?
You need to have following nesting if you want to establish background prerequisites for all your tabular based facts:
(against-background [...]
(tabular
(fact ...)
?... ?...))
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(against-background
[(fn-a) => {:a 1 :b 2 :c 3}]
(tabular
(fact
(fn-b ?k) => ?v)
?k ?v
:a 1
:b 3
:c 3))
(check-facts)
;; => All checks (3) succeeded.
If you want to have a background prerequisite per each tabular case you need to nest it as following:
(tabular
(against-background [...]
(fact ...))
?... ?...)
It's important to have the table just under tabular level, not nested in against-background or fact.
For example:
(require '[midje.repl :refer :all])
(defn fn-a []
(throw (RuntimeException. "Not implemented")))
(defn fn-b [k]
(-> (fn-a) (get k)))
(tabular
(against-background
[(fn-a) => {?k ?v}]
(fact
(fn-b ?k) => ?v))
?k ?v
:a 1
:b 2
:c 3)
(check-facts)
;; => All checks (3) succeeded.
In your code it looks like the tabular data is not positioned correctly (parentheses, brackets and curly braces are not balanced correctly so it's impossible to say what exactly is incorrect).

mocking a function call in midje

Say I have a function
(defn extenal_api_fn [stuff]
... do things....
)
(defn register_user [stuff]
(external_api_fn stuff))
And then a test
(def stuff1
{:user_id 123 })
(def stuff2
{:user_id 234})
(background (external_api_fn stuff1) => true
(with-redefs [external_api_fn (fn [data] (println "mocked function"))]
(register_user stuff1) => true)
(register_user stuff2) => true)
(facts "stuff goes here"
(fact "user that registers correctly
(= 1 1) => truthy)
(fact "user that has a registration failure"
(= 1 2) => falsy))
This fails with
"you never said #'external_api_fn" would be called with these arguments:
contents of stuff1
What would be a good way to stub this function call (in only some cases) in order to simulate an internal transaction failure.
You could use Midje's provided:
(fact
(register_user stuff1) => :registered
(provided
(extenal_api_fn stuff1) => :registered))
(fact
(register_user stuff2) => :error
(provided
(external_api_fn stuff2) => :error))
You can also stub a function to return a value no matter input parameters by using anything in place of the function argument:
(fact
(register_user stuff2) => :error
(provided
(external_api_fn anything) => :error))

rails3 content_tag with static attributes

The following is being properly generated into HTML code
<%= content_tag(:span, (t 'hints.h'), :class => "has-tip", :title => (t 'hints.s') ) %>
But I am trying to generate
<span data-tooltip aria-haspopup="true" class="has-tip" title="title bla bla">translated h</span>
and have found no way to generate these span attributes data-tooltip aria-haspopup="true" They cannot be part of the options hash given one has only a name... and the second one has a dash which impedes from defining it as a symbol :aria-haspopup
I suggest that you use the following:
content_tag(:span, t('hints.h'), :class => 'has-tip', :title => t('hints.s'), :'aria-haspopup' => true, :'data-tooltip' => '')
Note that you can use the dash character in symbols if you enclose them in quotes.
The data attribute you could also specify as nested hash like :data => {:tooltip => ''} instead of :'data-tooltip' => '', use whatever you prefer.
As for the boolean attribute data-tooltip, setting the value to an empty string is as good as omitting it (and your best option with Rails 3 ;)). See also:
http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#boolean-attributes

sunspot surely this can be done with some ruby magic?

I need to access my rails model instance from inside the searchable block as indicated below.
class Product
include MongoMapper::Document
include Sunspot::Rails::Searchable
key :field_names, Array
searchable do |ss|
self.field_names.each do |field|
ss.double field[:name] do
field[:value]
end
end
end
end
does anyone know how to do this via Sunspot ?
I have a field_names array on each product instance that is different per product so i need to access it.
Thanks a lot
Rick
you mean this?
def Foo
attr_accessible :id, :title
def fields
['something']
end
searchable do
integer :id
string :title
string :fields, :multiple => true do
self.fields
end
end
end
well inside there, you're inside a different evaluation context (Solr::DSL or something like that). That's to provide the ability to have those keywords like "integer, string". Looks like you're trying to evaluate dynamic attributes/filters .. .. so see my modified response (below)
you mean this?
def Foo
attr_accessible :id, :title
#fields_to_dynamically_add = ['title']
searchable do |s|
s.integer :id
s.string :title
#fields_to_dynamically_add.each do |f|
s.string f.to_sym
end
end
end
PS: have not added fields to searchable blocks dynamically every myself (although the above works)