Variable Arguments for Riemann project function - clojure

So I am working on a stream function which will sum up some metrics for several events. To do this I use the project function and it looks something like this:
(project [(func (:service event) (nth service-list 0))
(func (:service event) (nth service-list 1))
(func (:service event) (nth service-list 2))]
(..))
service-list is a argument of the surrounding function, which contain a vector of services that must be added together. func is a function that takes two arguments and returns a true/false Using the above snippet works fine, but I would like to be able to simplify it so that a vector that is larger or smaller than 3 elements would work.
So far I have this:
(project (mapv (fn[service] (
`func (:service event) ~service)
) service-list)))
(..)
which I think returns a vector of functions which are unevaluated. I went with this approach once I realised that project is a macro. No idea if I am doing the right thing...

The problem you are facing is that you can not dynamically assemble just the arguments of a macro call. Therefore you need to dynamically assemble the whole macro call.
You can build a clojure form and call eval on it. Take the following.
(let [args (mapv #(list 'func (:service 'event) %) service-list)
form (list 'project args (...))]
(eval form))
Or, you could also use the riemann.streams/project* function to use predicate functions instead of where expressions.

Related

Evaluate symbol passed to def in Clojure

I am working through Clojure for the Brave and True. In the chapter on macros there is this exercise:
Write a macro that defines an arbitrary number of attribute-retrieving functions using one macro call. Here’s how you would call it:
(defattrs c-int :intelligence
c-str :strength
c-dex :dexterity)
What these functions do is retrieve a value from a map. For example given: (def character {:name "Travis", :intelligence 20, :strength 23, :dexterity 13})
The result of (c-int character) would be 20 of course such a function could easily be defined as (def c-int #(:intelligence %))
This is the solution I came up with to the problem:
(defmacro defattrs
[& attributes]
`(let [attribute-pairs# (partition 2 (quote ~attributes))]
(map (fn [[function-name# attribute-key#]]
(def function-name# #(attribute-key# %)))
attribute-pairs#)))
The problem I am having is that def uses the generated symbol name instead of what it resolves to to define the function (which in hindsight makes sense given the usage of def). My attempts to use expressions with defining functions such as:
(let [x ['c-int :intelligence]]
(def (first x) #((second x) %)))
Have resulted in this error: CompilerException java.lang.RuntimeException: First argument to def must be a Symbol, compiling:(/tmp/form-init5664727540242288850.clj:2:1)
Any ideas on how I can achieve this?
There are ordinary manipulations that you do with the attributes parameter that don't need to be generated as forms:
splitting the attributes into attribute-pairs; and
defining the function to generate a def form for each pair.
Applying the above to your code, we get ...
(defmacro defattrs [& attributes]
(let [attribute-pairs (partition 2 attributes)]
(map (fn [[function-name attribute-key]]
`(def ~function-name #(~attribute-key %)))
attribute-pairs)))
The scope of the back-quote is restricted to the def we wish to generate.
The values of the function-name and attribute-key parameters of the function are inserted into the def form.
There is one problem remaining.
The result of the map is a sequence of def forms.
The first one will be interpreted as a function to
apply to the rest.
The solution is to cons a do onto the front of the sequence:
(defmacro defattrs [& attributes]
(let [attribute-pairs (partition 2 attributes)]
(cons 'do
(map (fn [[function-name attribute-key]]
`(def ~function-name ~attribute-key))
attribute-pairs))))
I've also abbreviated #(~attribute-key %) to the equivalent ~attribute-key within the back-quoted form.
Let's see what the expansion looks like:
(macroexpand-1 '(defattrs dooby :brrr))
;(do (def dooby :brrr))
Looks good. Let's try it!
(defattrs gosh :brrr)
(gosh {:brrr 777})
;777
It works.
You have found the use-case for the back-quote and tilde. Just try this:
(let [x ['c-int :intelligence]]
(eval `(def ~(first x) #(~(second x) %))))
(def character {:name "Travis", :intelligence 20, :strength 23, :dexterity 13})
(c-int character) => 20
The back-quote is similar to the single-quote in that it makes the next form into a data structure of lists, symbols, etc. The difference is that the data structure is intended to be used as a template, where internal bits can be substituted using the tilde. The cool part is that the tilde doesn't just substitute items, but works for live code that can be any arbitrary Clojure expression.

Creating a Clojure macro that uses a string to call a java function

So I'm trying to make a Clojure macro that makes it easy to interop with Java classes utilizing the Builder pattern.
Here's what I've tried so far.
(defmacro test-macro
[]
(list
(symbol ".queryParam")
(-> (ClientBuilder/newClient)
(.target "https://www.test.com"))
"key1"
(object-array ["val1"])))
Which expands to the below
(.
#object[org.glassfish.jersey.client.JerseyWebTarget 0x107a5073 "org.glassfish.jersey.client.JerseyWebTarget#107a5073"]
queryParam
"key1"
#object["[Ljava.lang.Object;" 0x16751ba2 "[Ljava.lang.Object;#16751ba2"])
The desired result is:
(.queryParam
#object[org.glassfish.jersey.client.JerseyWebTarget 0x107a5073 "org.glassfish.jersey.client.JerseyWebTarget#107a5073"]
"key1"
#object["[Ljava.lang.Object;" 0x16751ba2 "[Ljava.lang.Object;#16751ba2"])
I guess the . is causing something to get evaluated and moved around? In which case the solution would to be to quote it. But how can I quote the results of an evaluated expression?
My goal is to convert maps into code that build the object by have the maps keys be the functions to be called and the values be the arguments passed into the Java functions.
I understand how to use the threading and do-to macros but am trying to make request building function data driven. I want to be able take in a map with the key as "queryParam" and the values as the arguments. By having this I can leverage the entirety on the java classes functions only having to write one function myself and there is enough of a 1 to 1 mapping I don't believe others will find it magical.
(def test-map {"target" ["https://www.test.com"]
"path" ["qa" "rest/service"]
"queryParam" [["key1" (object-array ["val1"])]
["key2" (object-array ["val21" "val22" "val23"])]] })
(-> (ClientBuilder/newClient)
(.target "https://www.test.com")
(.path "qa")
(.path "rest/service")
(.queryParam "key1" (object-array ["val1"]))
(.queryParam "key2" (object-array ["val21" "val22" "val23"])))
From your question it's not clear if you have to use map as your builder data structure. I would recommend using the threading macro for working directly with Java classes implementing the builder pattern:
(-> (ClientBuilder.)
(.forEndpoint "http://example.com")
(.withQueryParam "key1" "value1")
(.build))
For classes that don't implement builder pattern and their methods return void (e.g. setter methods) you can use doto macro:
(doto (Client.)
(.setEndpoint "http://example.com")
(.setQueryParam "key1" "value1"))
Implementing a macro using a map for encoding Java method calls is possible but awkward. You would have to keep each method arguments inside a sequence (in map values) to be a able to call methods with multiple parameters or have some convention for storing arguments for single parameter methods, handling varargs, using map to specify method calls doesn't guarantee the order they will be invoked etc. It will add much complexity and magic to your code.
This is how you could implement it:
(defmacro builder [b m]
(let [method-calls
(map (fn [[k v]] `(. (~(symbol k) ~#v))) m)]
`(-> ~b
~#method-calls)))
(macroexpand-1
'(builder (StringBuilder.) {"append" ["a"]}))
;; => (clojure.core/-> (StringBuilder.) (. (append "a")))
(str
(builder (StringBuilder.) {"append" ["a"] }))
;; => "a"

Idiomatic way to proxy named parameters in Clojure

I need a function that thinly wraps amazonica's sqs/receive-message in order to add a default wait time. The function requires a queue URL, and then accepts any number of optional named parameters, which should be passed along to sqs/receive-message untouched. I would like to call it like this:
(my-receive-message "https://sqs.us-east-1.amazonaws.com/123/test-q"
:max-number-of-messages 10
:delete true)
This should result in a call to sqs/receive-message like this:
(sqs/receive-message :queue-url "https://sqs.us-east-1.amazonaws.com/123/test-q"
:wait-time-seconds 20
:max-number-of-messages 10
:delete true)
This is something I find myself wanting to do fairly often, but I haven't found a nice way yet. Is there an idiomatic way to do this?
Use apply over the merged parameters.
(defn my-receive-message
[url & {:as args}]
(apply sqs/receive-message (-> {:queue-url url
:wait-time-seconds 20}
(merge args)
seq
flatten)))
You could always write a macro:
(defmacro my-receive-message [url & opts]
`(sqs/receive-message
~#(flatten (seq (merge {:queue-url url :wait-time-seconds 20}
(apply hash-map opts))))))
(Note that this does pretty much exactly the same thing as Guillermo's function. The main difference is that you don't have to apply sqs/receive-message -- the unquote-splicing (~#) takes care of the apply part implicitly.)

Clojure: How does filter interact with logic?

I'm trying to learn Clojure, so I figured a good way to start would be to work through the project Euler challenges with it, and the first challenge is summing all of the number below 1000 that are divisible by 3 or 5.
My original code was:
(defn div3 [input-no] (zero? (mod input-no 3)))
(defn div5 [input-no] (zero? (mod input-no 5)))
(reduce + (filter (or div3 div5) (range 1 1000)))
But that didn't work, and it turned out that the filter would just return a list of numbers divisible by 3, and not those divisible by 5.
I changed my code to:
(defn div3or5 [input-no] (or (div3 input-no) (div5 input-no)))
(reduce + (filter div3or5 (range 1 1000)))
Which got the right result, but I don't know why my original code didn't work.
Could anyone shed some light on this?
The problem you are running into is that filter expects a predicate (a function taking an input and returning true or false) as its first argument. But while div3 and div5 are functions you can't simply combine them with or. You need to construct a new function that takes one argument and feeds this to both div3 and div5 and calls or and the results of both.
Fortunately this is easy to do in Clojure, try
(filter #(or (div3 %) (div5 %)) (range1 1000))
#() is shorthand for defining a function inline (also called a lambda) and you can get to the first argument with %1 to the second with %2 and so on. If there is only one argument then you can use % for %1 see this question.
You may also want to understand that #() is just syntactic sugar for the fn form which
looks like this: (fn [arg1 arg2 ... & restArgs] (forms)). #() has some limitations (for example it can't be nested).
If you just evaluate (or div3 div5) in the REPL you can see what is happening:
=> (or div3 div5)
#<user$div3 user$div3#73305c>
That is, or is evaluating to the function div3 (which filter is then using, giving the behaviour you describe).
The reason for this is or will return its first non-falsy argument (i.e. the first argument that isn't nil or false); in this case, the arguments are two function objects and a function object is not nil or false.
To put it another way, the or is happening on the functions themselves, not the results of the functions. As Paul said, you can use an anonymous function to make or act on the results rather than the functions themselves.

One argument, many functions

I have an incoming lazy stream lines from a file I'm reading with tail-seq (to contrib - now!) and I want to process those lines one after one with several "listener-functions" that takes action depending on re-seq-hits (or other things) in the lines.
I tried the following:
(defn info-listener [logstr]
(if (re-seq #"INFO" logstr) (println "Got an INFO-statement")))
(defn debug-listener [logstr]
(if (re-seq #"DEBUG" logstr) (println "Got a DEBUG-statement")))
(doseq [line (tail-seq "/var/log/any/java.log")]
(do (info-listener logstr)
(debug-listener logstr)))
and it works as expected. However, there is a LOT of code-duplication and other sins in the code, and it's boring to update the code.
One important step seems to be to apply many functions to one argument, ie
(listen-line line '(info-listener debug-listener))
and use that instead of the boring and error prone do-statement.
I've tried the following seemingly clever approach:
(defn listen-line [logstr listener-collection]
(map #(% logstr) listener-collection))
but this only renders
(nil) (nil)
there is lazyiness or first class functions biting me for sure, but where do I put the apply?
I'm also open to a radically different approach to the problem, but this seems to be a quite sane way to start with. Macros/multi methods seems to be overkill/wrong for now.
Making a single function out of a group of functions to be called with the same argument can be done with the core function juxt:
=>(def juxted-fn (juxt identity str (partial / 100)))
=>(juxted-fn 50)
[50 "50" 2]
Combining juxt with partial can be very useful:
(defn listener [re message logstr]
(if (re-seq re logstr) (println message)))
(def juxted-listener
(apply juxt (map (fn [[re message]] (partial listner re message))
[[#"INFO","Got INFO"],
[#"DEBUG", "Got DEBUG"]]))
(doseq [logstr ["INFO statement", "OTHER statement", "DEBUG statement"]]
(juxted-listener logstr))
You need to change
(listen-line line '(info-listener debug-listener))
to
(listen-line line [info-listener debug-listener])
In the first version, listen-line ends up using the symbols info-listener and debug-listener themselves as functions because of the quoting. Symbols implement clojure.lang.IFn (the interface behind Clojure function invocation) like keywords do, i.e. they look themselves up in a map-like argument (actually a clojure.lang.ILookup) and return nil if applied to something which is not a map.
Also note that you need to wrap the body of listen-line in dorun to ensure it actually gets executed (as map returns a lazy sequence). Better yet, switch to doseq:
(defn listen-line [logstr listener-collection]
(doseq [listener listener-collection]
(listener logstr)))