How to send a postwalk-replace function to an agent - clojure

I am trying to send postwalk-replace function to an agent containing an s-expression.
(def myagent (agent '(+ (* 2 x) 2)))
(send myagent postwalk-replace {'x 3})
But instead of the replacement I get the following error:
IllegalArgumentException contains? not supported on type:
clojure.lang.PersistentList
How can I send postwalk-replace and its arguments to an agent?

(send a f x y) is the same as (send a #(f % x y)) - it passes the agent's current state as the first argument to f. Contrarily, postwalk-replace accepts the replacement function first, followed by the thing to replace within. So, you will have to pass a function to the agent that intermediates between those to: (send a #(postwalk-replace '{x 3} %)).

Related

Clojure call series of functions and store their return values

I'm building a datomic schema and have the following at the foot of my clj file which defines and transacts schema and initial data. The functions being called below each call d/transact.
(defn recreate-database []
"To recreate db after running delete-database in bin/repl"
(pt1-transact-schema)
(pt1-transact-data)
(pt2-transact-schema)
(pt2-transact-data)
(pt3-transact-schema)
(pt3-transact-data))
By default we only see the return value of the last form, but I'd like to see, or save, the result of each of the six function calls.
Wondering what a nice way to do this is.
Thought of something like (map (comp println eval) [functions]), but that's not right.
there is also a nice functional composition function called juxt:
user> ((juxt + - * /) 1 2)
;;=> [3 -1 2 1/2]
user> ((juxt (constantly 1) (constantly 2) (constantly 3)))
;;=> [1 2 3]
or in your case:
(def recreate-database (juxt pt1-transact-schema
pt1-transact-data
pt2-transact-schema
pt2-transact-data
pt3-transact-schema
pt3-transact-data))
You could try this:
(defn recreate-database []
"To recreate db after running delete-database in bin/repl"
(mapv #(%) [pt1-transact-schema
pt1-transact-data
pt2-transact-schema
pt2-transact-data
pt3-transact-schema
pt3-transact-data]))
The expression #(%) is a shorthand notation for a lambda function that takes one argument, representing a function, and calls that function. If you find it more readable, you can replace that expression by (fn [f] (f)).
With datomic, all you need is a connection and a list of tx-data. Then you can use map to return the transact result on each step (i.e. each tx-data):
(defn recreate-database [conn & tx-data]
(->> tx-data
(map (partial d/transact conn))
doall))

Passing defrecord into function clojure

I'm currently working on a route planning robot in Clojure. The robot takes in a parcel which has a set of stops contained within it and then this robot is passed into a function which calculates the shortest route.
(defn journey [start end]
(alg/pprint-path (alg/shortest-path all-edges {:start-node start, :end-node end, :cost-attr :weight})))
(defn fullpath [& stops]
(doall (map (fn [a b] (journey a b)) stops (rest stops) )))
The two functions above calculate the shortest route between stops and print it out.
;;passed into robot
(defrecord Parcel [start
end
home])
;;passed into robotroute to plan journey of robot
(defrecord Robot [stops])
;;computes the path
(defn robotroute [robot]
(def stops (:stops robot))
(fullpath stops))
(def task1parcel (Parcel. :main-office :r131 :main-office))
(def task1robot (Robot. task1parcel))
(def task1 (robotroute task1robot))
(task1)
Above is my code for creating the robot and parcel. Robotroute is the function I am passing the robot into which is meant to strip out the stops and plan the route using fullpath.
All of the functions can be defined etc. However when trying to run task 1 I get the following error.
ClassCastException clojure.lang.LazySeq cannot be cast to clojure.lang.IFn funcprog2.core/eval13519 (form-init1291893531842170235.clj:1)
Can anyone assist with fixing this error?
Also moving forward I want a robot to hold multiple parcels so it can do more than one delivery in a row, what would be the best plan to move forward with this?
(defn fullpath [& stops]
(doall (map (fn [a b] (journey a b)) stops (rest stops) )))
function fullpath return a lazyseq . (task1) will evaluate this lazeseq again Maybe you could just get your result by putting "task1" in repl.
For example (def list '(1 2 3)) list is equal to '(1 2 3). (list) will end up getting "ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn"
Another point is that try
(defn robotroute [robot]
(let [stops (:stops robot)] (fullpath stops)))
in clojure a suggestion is that : use let rather than def to bind variable when defining a function because def means global
(def stops inside roboroute - shouldn't that be a let ?
(robotroute task1robot) returns whatever fullpath returns, which is (doall (map ... - it's a sequence - and sequences are not callable/not a function (as the error suggests). Therefor invoking it like (task1) fails.

Named parameters in Yesql

How can one use named parameters when running queries using Yesql? For instance, given the following query
-- name: example
select * from table1 where col1=:value1 and col2=:value2
i'd like to use the following code:
(defquery q "query.sql")
(q db {:value1 "A" :value2 "B"})
Right now, Yesql treats the map as a single positional parameter.
Thanks!
The current version of Yesql - 0.4.0 - does not support easily this. The named parameters are only for documentation. You can see them if you do (doc q), but you can't use them to run the query. The author of Yesql has mentioned that they're planning exactly what you want for Yesql 0.5.
What I've done in the same situation is to just manually wrap the query into another function:
(defn q1 [db {:keys [value1 value2]}] (q db value1 value2))
It's a bit cumbersome, but it works.
It is possible to do this automatically even without improving Yesql, but... this is going to be a bit of a hack. You probably do not want to do this.
We can define a function similar to apply that uses the query function's metadata to get the arguments in the right order from the map of named parameters.
(defn apply-with-arglist-fn [f arglist & args]
(let [nargs (butlast args)
kwargs (last args)]
(apply f (concat nargs (map #(get kwargs (keyword %))
(drop (count nargs) arglist))))))
;; Let's define a macro to make calling this a bit easier.
(defmacro apply-with-arglist [f & args]
"Applies fn f to the arguments in a map by matching them with f's
arglist. Works like apply, except that the last parameter should be
a map of keyword arguments. For example:
(defn f [a b c] ...)
(apply-with-arglist f 1 {:b 2 :c 3})
This is equivalent to calling (f 1 2 3)."
`(apply-with-arglist-fn ~f (first (:arglists (meta (var ~f)))) ~#args))
You can use this to run the query:
(apply-with-arglist q db {:value1 "A" :value2 "B"})
Really, though, there should be error handling and dealing with corner cases. A better approach would be to see if you can help the Yesql author to get Yesql 0.5 ready.

How to make java API wrapper work with different function arities?

I'm writing a small wrapper for a Java API, and I create a listener like this
(defn conv-listener [f]
(proxy [com.tulskiy.keymaster.common.HotKeyListener] [] (onHotKey [hotKey] (f))))
Is there a way in which I can make this work whether the function f accepts 1 or zero arguments. (Ie. if f does not accept arguments, just call it with (f), if it accepts an argument - which will be the value of the hotkey in this case - call it with (f hotKey))?
No. Just call (f hotKey) all the time, and if someone wants to use a function that ignores hotKey then they can just pass something like (fn [_] (...do whatever...)).
This is how we ended up solving it (pull request from Nic Marsh):
(defn arg-count [function]
"Counts the number of arguments the given function accepts"
(let [method (first (.getDeclaredMethods (class function)))
parameters (.getParameterTypes method)]
(alength parameters)))
(defn call-with-correct-args [function & args]
"Call the given function on all given args that it can accept"
(let [amount-accepted (arg-count function)
accepted-args (take amount-accepted args)]
(apply function accepted-args)))
(defn- conv-listener [function]
"Takes a function with one argument, which will get passed the keycode, and creates a listener"
(proxy [com.tulskiy.keymaster.common.HotKeyListener] []
(onHotKey [hotKey] (call-with-correct-args function hotKey))))
http://github.com/houshuang/keymaster-clj

How to get the metadata of clojure function arguments?

Is there a way to generically get metadata for arguments to a function in clojure? The answer posted in this question does not, actually, work in general:
user> (defn foo "informative dox!" [] 1)
#'user/foo
user> (defmacro get-docs [func] `(:doc (meta (var ~func))))
#'user/get-docs
user> (get-docs foo)
"informative dox!"
user> (get-docs (identity foo))
; Evaluation aborted.
user> (defn process-docs [f] (let [docs (get-docs f)] (reverse docs)))
; Evaluation aborted.
The second-to-last line doesn't work because you can't call var on the list (identity foo), and the last line doesn't even compile because the compiler complains about being unable to resolve f.
Most of the solutions for this problem I've found rely on the idea that you have access to the symbol in the function's definition, or something like that, so that you can do something like (resolve 'f) or (var f). But I want something that I can use on the argument to a function, where you don't know that information.
Essentially, I'd like an expression I can put in place of the question marks below to get the metadata of #'map:
(let [x map] (??? x))
its a mouthful though possible:
(let [x map]
(:doc (meta (second (first (filter #(and (var? (second %))
(= x (var-get (second %))))
(ns-map *ns*)))))))
produces the desired result:
"Returns a lazy sequence consisting of the result of applying f to the
set of first items of each coll, followed by applying f to the set
of second items in each coll, until any one of the colls is\n exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments."
under the hood Namespaces are essentially maps of names to vars and the vars contain functions. you can search the contents of these vars for the one that matches the function you are seeking and then look at it's associated var and get the metadata from that var.