core.typed - Trying to annotate a Record in dependency - clojure

I'm trying to annotate the Element record in clojure.data.xml which is:
(defrecord Element [tag attrs content])
I've annotated it as follows:
(t/ann-record Element [tag :- t/Keyword attrs :- (t/HMap :complete? false)
content :- (t/Vec Element)])
And I have the following function which doesn't type check:
(t/ann get-content [Element -> (t/Vec Element)])
(defn get-content [xml]
(:content xml))
with 'Expected: t/Vec clojure.data.xml.Element Actual: t/Any'
I've also tried replacing that with (get xml :content) but it fails with the same output.
I wonder what I'm doing wrong :D

It might be that you need to fully qualify Element in your ann-record call (I'm assuming your code is not actually in the clojure.data.xml namespace).
The docs for ann-record say
...
; a record in another namespace
(ann-record another.ns.TheirRecord
[str :- String,
vec :- (Vec Number)])
...
So in this particular case:
(t/ann-record clojure.data.xml.Element
[tag :- t/Keyword
attrs :- (t/HMap :complete? false)
content :- (t/Vec Element)])

Related

How to create a Clojure function that returns a Hiccup structure?

Imagine I want to write a Clojure function that returns a Hiccup structure equivalent to <h3>Hello</h3>.
How can I do it?
I tried
(defn render-location-details
[cur-location]
(let []
(list :h3 "Hello")
)
)
and
(defn render-location-details
[cur-location]
(let []
[:h3 "Hello"]
)
)
but in both cases got the error message (:h3 "Location X") is not a valid element name..
Update 1: I am calling the above function from this one:
(defn generate-location-details-report
[all-locations]
(let
[
hiccup-title [:h2 "Locations"]
hiccup-body (into []
(map
render-location-details
all-locations)
)
]
(str
(html hiccup-title)
hiccup-body
)
)
)
There is a collection all-locations. For each element of it, I want to create a section in HTML (with the h3) header (hiccup-body), prepend the title (hiccup-title) and convert all of this to HTML.
The hiccup html function will take a sequence of tags and render them as a string.
(let [locations ["one" "two" "three"]
title-html [[:h2 "Locations"]]
location-html (map (fn [location] [:h3 location]) locations)]
(html (concat title-html location-html)))
"<h2>Locations</h2><h3>one</h3><h3>two</h3><h3>three</h3>"
The first render-location-details doesn't work because a list isn't a vector, and so Hiccup won't render it as a tag.
The second render-location-details is OK and does what you want. The empty (let [] binding is unnecessary. However Hiccup is then confused by putting hiccup-body (into [] - it is trying to understand your vector of location tags as a tag, because as far as Hiccup is concerned vector = tag.

Encoding records as JSON objects with additional type field in Clojure

Cheshire's custom encoders seem suitable for this problem and I wrote a little helper function:
(defn add-rec-encoder [Rec type-token]
(add-encoder Rec
(fn [rec jg] (.writeString jg
(str (encode-map (assoc rec :type type-token) jg))))))
(defrecord A [a])
(add-rec-encoder A "A")
(encode (->A "abc"))
But it produces a strange trailing "".
=> {"a":"abc","type":"A"} ""
What is causing this? And is there another approach worth considering (I also need to be able to decode back to a record based on this type-token)?
(encode-map ... jg) directly writes the encoded map to the JSON generator jg, then returns nil.
This means that, your call to writeString is actually:
(.writeString jg (str nil))
which, since (str nil) is "", will encode and append exactly that to the JSON generator. The correct encoder logic would be:
(defn add-rec-encoder [Rec type-token]
(add-encoder Rec
(fn [rec jg]
(encode-map (assoc rec :type type-token) jg))))

Clojure: Dynamically create functions from a map -- Time for a Macro?

I have a function that begins like this:
(defn data-one [suser]
(def suser-first-name
(select db/firstNames
(fields :firstname)
(where {:username suser})))
(def suser-middle-name
(select db/middleNames
(fields :middlename)
(where {:username suser})))
(def suser-last-name
(select db/middleNames
(fields :lastname)
(where {:username suser})))
;; And it just continues on and on...
)
Of course, I don't like this at all. I have this pattern repeating in many areas in my code-base and I'd like to generalize this.
So, I came up with the following to start:
(def data-input {:one '[suser-first-name db/firstNames :firstname]
'[suser-middle-name db/middleNames :middlename]
'[suser-last-name db/lastNames :lastname]})
(defpartial data-build [data-item suser]
;; data-item takes the arg :one in this case
`(def (data-input data-item)
(select (data-input data-item)
(fields (data-input data-item))
(where {:username suser}))))
There's really a few questions here:
-- How can I deconstruct the data-input so that it creates x functions when x is unknown, ie. that the values of :one is unknown, and that the quantities of keys in data-input is unknown.
-- I'm thinking that this is a time to create a macro, but I've never built one before, so I am hesitant on the idea.
And to give a little context, the functions must return values to be deconstructed, but I think once I get this piece solved, generalizing all of this will be doable:
(defpage "/page-one" []
(let [suser (sesh/get :username)]
(data-one suser)
[:p "Firat Name: "
[:i (let [[{fname :firstname}] suser-first-name]
(format "%s" fname))]
[:p "Middle Name: "
[:i (let [[{mname :emptype}] suser-middle-name]
(format "%s" mname))]
[:p "Last Name: "
[:i (let [[{lname :months}] suser-last-name]
(format "%s" lname))]]))
Some suggestions:
def inside a function is really nasty - you are altering the global environment, and it can cause all kinds of issues with concurrency. I would suggest storing the results in a map instead.
You don't need a macro here - all of the data fetches can be done relatively easily within a function
I would therefore suggest something like:
(def data-input [[:suser-first-name db/firstNames :firstname]
[:suser-middle-name db/middleNames :middlename]
[:suser-last-name db/lastNames :lastname]])
(def data-build [data-input suser]
(loop [output {}
items (seq data-input)]
(if items
(recur
(let [[kw db fieldname] (first items)]
(assoc output kw (select db (fields fieldname) (where {:username suser}))))
(next items))
output)))
Not tested as I don't have your database setup - but hopefully that gives you an idea of how to do this without either macros or mutable globals!
Nice question. First of all here's the macro that you asked for:
(defmacro defquery [fname table fields ]
(let [arg-name (symbol 'user-name)
fname (symbol fname)]
`(defn ~fname [~arg-name]
(print ~arg-name (str ~# fields)))))
You can call it like that:
(defquery suser-first-name db/firstNames [:firstname])
or if you prefer to keep all your configurations in a map, then it will accept string as the first argument instead of a symbol:
(defquery "suser-first-name" db/firstNames [:firstname])
Now, if you don't mind me recommending another solution, I would probably chose to use a single function closed around configuration. Something like that:
(defn make-reader [query-configurations]
(fn [query-type user-name]
(let [{table :table field-names :fields}
(get query-configurations query-type)]
(select table
(apply fields field-names)
(where {:username suser})))))
(def data-input {:firstname {:table db/firstNames :fields :firstname}
:middlename {:table db/middleNames :fields :middlename}
:lastname {:table db/lastNames :fields :lastname}})
(def query-function (make-reader data-input))
;; Example of executing a query
(query-function :firstname "tom")
By the way there's another way to use Korma:
;; This creates a template select from the table
(def table-select (select* db/firstNames))
;; This creates new select query for a specific field
(def first-name-select (fields table-select :firstname))
;; Creating yet another query that filters results by :username
(defn mkselect-for-user [suser query]
(where query {:username suser}))
;; Running the query for username "tom"
;; I fully specified exec function name only to show where it comes from.
(korma.core/exec (mkselect-for-user "tom" first-name-select))
For more information I highly recommend looking at Korma sources.

Looping If And Until Result Found and Then Exiting

I do not think the key-pres? function below is working the way I expect it to. First, here is the input data.
Data from which cmp-val derived:
["2" "000-00-0000" "TOAST" "FRENCH" "" "M" "26-Aug-99" "" "ALL CARE PLAN" "MEDICAL"]
Data that is missing the key (ssn).
["000-00-0000" "TOAST " "FRENCH " "RE-PART B - INSURED "]
The problem is, if I make one of the input's 000-00-0000 something else, I should
see that conj'd onto a log vector. I don't, and I don't see it printed with the if-not empty?.
(defn is-a-in-b
"This is a helper function that takes a value, a column index, and a
returned clojure-csv row (vector), and checks to see if that value
is present. Returns value or nil if not present."
[cmp-val col-idx csv-row]
(let [csv-row-val (nth csv-row col-idx nil)]
(if (= cmp-val csv-row-val)
cmp-val
nil)))
(defn key-pres?
"Accepts a value, like an index, and output from clojure-csv, and looks
to see if the value is in the sequence at the index. Given clojure-csv
returns a vector of vectors, will loop around until and if the value
is found."
[cmp-val cmp-idx csv-data]
(let [ret-rc
(for [csv-row csv-data
:let [rc (is-a-in-b cmp-val cmp-idx csv-row)]
:when (true? (is-a-in-b cmp-val cmp-idx csv-row))]
rc)]
(vec ret-rc)))
(defn test-key-inclusion
"Accepts csv-data file and an index, a second csv-data param and an index,
and searches the second csv-data instances' rows (at index) to see if
the first file's data is located in the second csv-data instance."
[csv-data1 pkey-idx1 csv-data2 pkey-idx2]
(reduce
(fn [out-log csv-row1]
(let [cmp-val (nth csv-row1 pkey-idx1 nil)]
(doseq [csv-row2 csv-data2]
(let [temp-rc (key-pres? cmp-val pkey-idx2 csv-row2)]
(if-not (empty? temp-rc)
(println cmp-val, " ", (nth csv-row2 pkey-idx2 nil), " ", temp-rc))
(if (nil? temp-rc)
(conj out-log cmp-val))))))
[]
csv-data1))
What I want the function to do is traverse data returned by clojure-csv (a vector of vectors). If cmp-val can be found at the cmp-idx location in csv-row, I'd like that
assigned to rc, and the loop to terminate.
How can I fix the for loop, and if not, what looping mechanism can I use to accomplish this?
Thank you.
you don't need true?, it specifically checks for the boolean true value;
don't repeat the call to is-a-in-b;
it would be more idiomatic (and readable) to use a-in-b? as the fn name;
I suggest simplifying the code, you don't really need that let.
Code:
(vec (for [csv-row csv-data
:let [rc (a-in-b? cmp-val cmp-idx csv-row)]
:when rc)]
rc))
But, this are just some general comments on code style... what you're implementing here is just a simple filter:
(vec (filter #(a-in-b? cmp-val cmp-idx %) csv-data))
Furthermore, this will return not only the first, but all matches. If I read your question right, you just need to find the first match? Then use some:
(some #(a-in-b? cmp-val cmp-idx %) csv-data)
UPDATE
Rereading your question I get the feeling that you consider for to be a loop construct. It's not -- it's a list comprehension, producing a lazy seq. To write a loop where you control when to iterate, you must use loop-recur. But in Clojure you'll almost never need to write you own loops, except for performance. In all other cases you compose higher-order functions from clojure.core.

Clojure Parse String

I have the following string
layout: default
title: Envy Labs
What i am trying to do is create map from it
layout->default
title->"envy labs"
Is this possible to do using sequence functions or do i have to loop through each line?
Trying to get a regex to work with and failing using.
(apply hash-map (re-split #": " meta-info))
user> (let [x "layout: default\ntitle: Envy Labs"]
(reduce (fn [h [_ k v]] (assoc h k v))
{}
(re-seq #"([^:]+): (.+)(\n|$)" x)))
{"title" "Envy Labs", "layout" "default"}
The _ is a variable name used to indicate that you don't care about the value of the variable (in this case, the whole matched string).
I'd recommend using clojure-contrib/duck-streams/read-lines to process the lines then split the fields from there. I find this method is usually more robust to errors in the file.