Method signature in REPL - clojure

I need to use a poorly documented Java library and it would help me if there was a way to see the signature of methods in REPL (for quick experiments). Consider the following:
user=> (import 'x.y.z.C)
user=> (show-method-signature 'C/m)
C/m String Integer String boolean
Is there a tricky method like show-method-signature already available?

The clojure.reflect library is your friend here.
(require '[clojure [reflect :as r]])
;; Return the method signature for methods matching a given regex.
;; Params:
;; cls - a class (eg. java.util.List) or an instance
;; method-name-regex - a regex string to match against the method name
(defn method-sig [cls method-name-regex]
(let [name-regex (re-pattern method-name-regex)]
(filter #(re-matches name-regex (str (:name %)))
(:members (r/reflect cls)))))
You can use it as follows:
=> (method-sig java.util.List "add")
;; returns
({:name add,
:return-type boolean,
:declaring-class java.util.LinkedList,
:parameter-types [java.lang.Object],
:exception-types [],
:flags #{:public}}
{:name add,
:return-type void,
:declaring-class java.util.LinkedList,
:parameter-types [int java.lang.Object],
:exception-types [],
:flags #{:public}})
=> (method-sig (java.util.LinkedList.) "add.*") ;; also works

Related

Apply a function to multiple values of a hashmap and return the result in an array

I have a definition:
(def class1 {:people ({:name "John" :age "25"} {:name "Harry" :age "23"} {:name "Peter" :age "24"})})
The result I want is a vector that looks like
[["John" "25"]
["Harry" "23"]
["Peter" "24"]]
If I call (map (-> class1 :people) [:name :age])
then I get the result ("Peter" "24"). Why do I only get the values from the last hashmap and not the others?
If I then call (into [] (map (-> class1 :people) [:name :age]))
then I get the result ["Peter" "24"]
What I need to do is run the into function again on the other two hashmaps and then put the 3 results into a vector but I don't know how to do this.
My problem is that when I run (map (-> class1 :people) [:name :age]), I get the last of the hashmaps. I believe that you have to do something like run a function on each of the hashmaps, one at a time and then each time put that vector into another vector to get the form that I am looking for.
Any help would be much appreciated
(def class1 {:people ({:name "John" :age "25"} {:name "Harry" :age "23"} {:name "Peter" :age "24"})})
defines class1 as the map:
{:people {:name "Peter" :age "24"}}
because a list of the form (a b c) evaluates a b and c in turn before evaluating to c. If you want your map to contain all the maps you need to quote it:
(def class1 {:people '({:name "John" :age "25"} {:name "Harry" :age "23"} {:name "Peter" :age "24"})})
To extract the values you want from this map you can use:
(mapv (juxt :name :age) (:people class1))

Clojure Spec on a vararg function

I am trying to write a spec for a merge function which takes a function and maps as an input and uses function to resolve the conflicts. However the spec i wrote for the function fails. I am trying to figure out how to write spec for such functions.
Below is the code snippet.
(require '[clojure.spec.test :as stest])
(require '[clojure.spec :as spec])
(defn deep-merge-with [func & maps]
(let [par-func (partial deep-merge-with func)]
(if (every? map? maps)
(apply merge-with par-func maps)
(apply func maps))))
(spec/fdef deep-merge-with
:args (spec/cat :func (spec/fspec :args (spec/cat :maps (spec/* map?))
:ret map?)
:maps (spec/cat :maps (spec/* map?)))
:ret map?)
(stest/instrument `deep-merge-with)
(deep-merge-with (fn [f s] s) {:a 1} {:a 2})
The spec error i am getting is:
clojure.lang.ExceptionInfo: Call to #'boot.user/deep-merge-with did not conform to spec:
In: [0] val: () fails at: [:args :func] predicate: (apply fn), Wrong number of args (0) passed to: user/eval22001/fn--22002
:clojure.spec/args (#function[boot.user/eval22001/fn--22002] {:a 1} {:a 2})
In your [:args :func] spec:
(spec/fspec :args (spec/cat :maps (spec/* map?)) :ret map?)
You're saying that the function must accept as arguments any number of maps and return a map. But the function you pass to deep-merge-with does not conform to that spec:
(fn [f s] s)
This function takes exactly two arguments, not an arbitrary number of maps.

clojure way to update a map inside a vector

What is the clojure way to update a map inside a vector e.g. if I have something like this, assuming each map has unique :name
(def some-vec
[{:name "foo"
....}
{:name "bar"
....}
{:name "baz"
....}])
I want to update the map in someway if it has :name equal to foo. Currently I'm using map, like this
(map (fn [{:keys [name] :as value}]
(if-not (= name "foo")
value
(do-something .....))) some-vec)
But this will loop through the entire vector even though I only update one item.
Keep the data as a map instead of a vector of map-records, keyed by :name.
(def some-data
{"foo" {:name "foo" :other :stuff}
"bar" {:name "bar" :other :stuff}
"baz" {:name "baz" :other :stuff}})
Then
(assoc-in some-data ["bar" :other] :things)
produces
{"foo" {:other :stuff, :name "foo"},
"bar" {:other :things, :name "bar"},
"baz" {:other :stuff, :name "baz"}}
in one go.
You can capture the basic manipulation in
(defn assoc-by-fn [data keyfn datum]
(assoc data (keyfn datum) datum))
When, for example,
(assoc-by-fn some-data :name {:name "zip" :other :fassner})
produces
{"zip" {:other :fassner, :name "zip"},
"foo" {:other :stuff, :name "foo"},
"bar" {:other :stuff, :name "bar"},
"baz" {:other :stuff, :name "baz"}}
Given that you have a vector of maps, your code looks fine to me. Your concern about "looping through the entire vector" is a natural consequence of the fact that you're doing a linear search for the :name and the fact that vectors are immutable.
I wonder whether what you really want is a vector of maps? Why not a map of maps?
(def some-map
{"foo" {...}
"bar" (...}
"baz" {...}}
Which you could then update with update-in?
Given this shape of the input data and unless you have an index that can tell you which indices the maps with a given value of :name reside at, you will have to loop over the entire vector. You can, however, minimize the amount of work involved in producing the updated vector by only "updating" the matching maps, rather than rebuilding the entire vector:
(defn update-values-if
"Assumes xs is a vector. Will update the values for which
pred returns true."
[xs pred f]
(let [lim (count xs)]
(loop [xs xs i 0]
(if (< i lim)
(let [x (nth xs i)]
(recur (if (pred x)
(assoc xs i (f x))
xs)
(inc i)))
xs))))
This will perform as many assoc operations as there are values in xs for which pred returns a truthy value.
Example:
(def some-vec [{:name "foo" :x 0} {:name "bar" :x 0} {:name "baz" :x 0}])
(update-values-if some-vec #(= "foo" (:name %)) #(update-in % [:x] inc))
;= [{:name "foo", :x 1} {:name "bar", :x 0} {:name "baz", :x 0}]
Of course if you're planning to transform the vector in this way with some regularity, then Thumbnail's and Paul's suggestion to use a map of maps will be a much more significant improvement. That remains the case if :name doesn't uniquely identify the maps – in that case, you could simply transform your original vector using frequencies and deal with a map of vectors (of maps with a given :name).
If you're working with vector, you should know index of element that you want to change, otherwise you have to traverse it in some way.
I can propose this solution:
(defn my-update [coll val fnc & args]
(let [index (->> (map-indexed vector coll)
(filter (fn [[_ {x :name}]] (= x val)))
ffirst)]
(when index
(apply update-in coll [index] fnc args))))
Where:
coll - given collection of maps;
val - value of field :name;
fnc - updating function;
args - arguments of the updating function.
Let's try it:
user> (def some-vec
[{:name "foo"}
{:name "bar"}
{:name "baz"}])
;; => #'user/some-vec
user> (my-update some-vec "foo" assoc :boo 12)
;; => [{:name "foo", :boo 12} {:name "bar"} {:name "baz"}]
user> (my-update some-vec "bar" assoc :wow "wow!")
;; => [{:name "foo"} {:name "bar", :wow "wow!"} {:name "baz"}]
I think that Thumbnail's answer may be quite useful for you. If you can keep your data as a map, these manipulations become much easier. Here is how you can transform your vector into a map:
user> (apply hash-map (interleave (map :name some-vec) some-vec))
;; => {"foo" {:name "foo"}, "bar" {:name "bar"}, "baz" {:name "baz"}}

Merge two complex data structures

I'm having trouble finding solution to the following problem:
Lets say I have a map:
(def defaults {
:name "John"
:surname "Doe"
:info {:date-of-birth "01-01-1980"
:registered [{:type "newsletter" :name "breaking news" }]}
})
And then I pass a similar structured map but I want to conjoin the vectors and overwrite the rest of the keys:
(def new {
:name "Peter"
:info {:date-of-birth "11-01-1986"
:registered [{:type "alert" :name "mobile-alert" }]}
})
And I want this result:
{:name "Peter"
:surname "Doe"
:info {:date-of-birth "11-01-1986"
:registered [{:type "newsletter" :name "breaking news" }
{:type "alert" :name "mobile-alert" }]}}
Now I can do this easily by using static syntax like:
(reduce conj (get-in defaults [:info :registered]) (get-in new [:info :registered]))
(There is probably a better way...) But I was hoping more of a dynamic function with the following properties:
Keep all keys from both maps, without knowing the structure
Update any keys with the values from the right map
if the val of a key is a vector, then conj the vector with the vector of the right map (if the appropriate key exists of course)
Thanks for the help in advance :)
You should definitely look at merge-with function. This is possible implementation:
(defn deep-merge [a b]
(merge-with (fn [x y]
(cond (map? y) (deep-merge x y)
(vector? y) (concat x y)
:else y))
a b))
Here's a possible implementation for this kind of functionality. It is at least a starting point, you may need some extra validations depending on the possible structure of your data (e.g. what if the overriding map's value is a vector but the value in the default map is not even a collection?).
(declare merge-maps)
(defn merge-map [x [k v]]
(cond (vector? v)
(assoc x k (vec (reduce conj (x k) v)))
(map? v)
(assoc x k (merge-maps (x k) v))
:esle
(assoc x k v)))
(defn merge-maps [x y]
(reduce merge-map x y))
(merge-maps defaults new)
;= {:info {:date-of-birth "11-01-1986",
;= :registered [{:name "breaking news", :type "newsletter"}
;= {:name "mobile-alert", :type "alert"}]},
;= :name "Peter",
;= :surname "Doe"}

Filter for Lists in Clojure

I am having a bit of difficulty with Lists in Clojure
I have a quick question concerning the filter function
Let's say I have a List composed of Maps
My code is:
(def Person {:name Bob } )
(def Person2 {:name Eric } )
(def Person3 {:name Tim } )
(def mylist (list Person Person2 Person3))
How would i go about filtering my list so that , for example: I want the list minus Person2 (meaning minus any map that has :name Eric)
Thank you very much to everybody helping me out. This is my last question I promise
For this purpose, it's better to use the 'remove' function. It takes a sequence, and removes elements on which it's predicate returns 'true'. It's basically the opposite of filter. Here is an example of it, and filter's usage for the same purposes, that I worked up via the REPL.
user> (def m1 {:name "eric" :age 32})
#'user/m1
user> (def m2 {:name "Rayne" :age 15})
#'user/m2
user> (def m3 {:name "connie" :age 44})
#'user/m3
user> (def mylist (list m1 m2 m3))
#'user/mylist
user> (filter #(not= (:name %) "eric") mylist)
({:name "eric", :age 32})
user> (remove #(= (:name %) "eric") mylist)
({:name "Rayne", :age 15} {:name "connie", :age 44})
As you can see, remove is a little bit cleaner, because you don't have to use not=. Also, when working with maps, you don't have to use the 'get' function unless you want it to return something special if a key isn't in the map. If you know the key you're looking for will be in the map, there is no reason to use 'get'. Good luck!
Suppose you have something like this:
(def Person {:name "Bob" } )
(def Person2 {:name "Eric" } )
(def Person3 {:name "Tim" } )
(def mylist (list Person Person2 Person3))
This would work:
(filter #(not= "Eric" (get % :name)) mylist)
user=> (filter (fn [person] (not= (person :name) "Eric")) mylist)
({:name "Bob"} {:name "Tim"})
or using a more compact syntax:
user=> (filter #(not= (% :name) "Eric") mylist)
({:name "Bob"} {:name "Tim"})