macro how deal with sql like fn use map - clojure

I want to use one macro to handle with seq parameter, but I don’t know how!
e.g.:
(defmacro create-table-def
[s]
`(let [ks# (keys (struct-map ~s))
sql# (map (fn [p#] (str (name p#) " varchar(20) ")) ks#)]
(str "create-table " '~s " ( "
(apply str (interleave seq# ", ")) " ) ") ))
I have multiple parameter invoke this macro, but not use map (map create-table-def ps) or reduce, what should I do to deal with.
I use a macro because I want to know the parameter symbol.

I came up with this version that is reasonably close to your version:
(defmacro create-table-def [table-name key-val-map]
(let [sql-part1 (vector "create table "
(name table-name)
"(")
sql-pre-part2 (apply vector
(map #(str (name (first %))
" "
(second %))
key-val-map))
sql-part2 (butlast (interleave sql-pre-part2 (repeat ", ")))
sql-part3 [");"]]
(apply str (concat sql-part1 sql-part2 sql-part3))))
(create-table-def my-table {foo "varchar(20)" bar "int"})
;"create table my-table(foo varchar(20), bar int);"
And if you really need to pass in a seq you can do this:
(defmacro create-table-def2 [[name & pairs]]
`(create-table-def ~name ~(apply array-map pairs)))
(create-table-def2 [my-table foo "varchar(20)" bar "int"])
;"create table my-table(foo varchar(20), bar int);"
Or just as direct arguments:
(defmacro create-table-def3 [name & pairs]
`(create-table-def ~name ~(apply array-map pairs)))
(create-table-def3 my-table foo "varchar(20)" bar "int")
;"create table my-table(foo varchar(20), bar int);"
More directly focussed towards the solutions however, (ab)using the printed representation of a map:
(use '[clojure.contrib.string :as str-utils])
(defmacro table-def [tname & things]
(str-utils/join " "
["create-table" (name tname) "("
(str-utils/drop 1
(str-utils/chop
(print-str (apply array-map things))))
");"]))
(table-def my-table foo "varchar(20)" bar "int")
;"create-table my-table ( foo varchar(20), bar int );"

Related

Compact way in Clojure to print a variable number of command line arguments?

I'm a newbie to Clojure. I think I'm trying to solve this procedurally and there must be a better (more functional) way to do this in Clojure...
The -main function can receive a variable number of 'args'. I would like to print them out but avoid an IndexOutOfBoundsException. I thought I could mimic Java and use a case fall-through to minimize the code involved, but that didn't work:
(defn -main [& args]
(println "There are" (str (count args)) "input arguments.")
(println "Here are args:" (str args))
(let [x (count args)]
(case x
(> x 0) (do
(print "Here is the first arg: ")
(println (nth args 0)))
(> x 1) (do
(print "Here is the 2nd arg: ")
(println (nth args 1)))
(> x 2) (do
(print "Here is the 3rd arg: ")
(println (nth args 2))))))
(doseq [[n arg] (map-indexed vector arguments)]
(println (str "Here is the argument #" (inc n) ": " (pr-str arg))))
map-indexes is like map but adds index number in the beginning.
So it goes item by item through arguments, packs index and item into a vector and by destructruing index number and item are mapped to [n arg].
Since clojure begins counting from 0, you use (inc n) to begin counting from 1. pr-str is pretty print string. The str joins all string components together.
there is also a handy formatting facility in clojure's core library: cl-format, which is the port of common lisp's format syntax. It includes a nice way to print out collections:
(require '[clojure.pprint :refer [cl-format]])
(let [args [:a :b :c :d]]
(cl-format true "~{here is the ~:r arg: ~a~%~}"
(interleave (rest (range)) args)))
;; here is the first arg: :a
;; here is the second arg: :b
;; here is the third arg: :c
;; here is the fourth arg: :d
some more information about what format can do is here

Return concatenated params in clojure

Need function or macro, which takes const parameter(route) and dynamic parameters (args) and return concatenated string of parameters:
user>(defn full-url [route & args] *need code* )
#'user/full-url
user> (def p1 "value1")
#'user/p1
user> (def p2 "value2")
#'user/p2
user> (def p3 "value3")
#'user/p3
user> (full-url "/init" p1 p2 p3)
"/init?p1=value1&p2=value2&p3value4"
Any ideas?
First, a macro to do what you want:
(defmacro full-url
[route & args]
`(let [var-names# (map #(str %1 "=") '~args)
var-vals# (list ~#args)
joined# (clojure.string/join "&" (map str var-names# var-vals#))]
(str ~route "?" joined#)))
Now, I would add that I do not think this is the best approach as it ties the names of your vars to the param names. IMO a better approach is to use a regular function that takes a map as a second argument, that has keywords and values. Such as:
(defn full-url-fn
[route params]
(->> params
(map #(str (name (first %)) "=" (second %)))
(clojure.string/join "&")
(str route "?")))
(full-url-fn "/init" {:p1 "value1" :p2 "value2"})
But, either way should work.

clojure.lang.Symbol cannot be cast to java.lang.CharSequence in macro

I'm writing a macro to allow pass the clauses as a parameter to functions:
(defmacro parse-cmd [command & body]
(let [parts (str/split command #" ")
cmd (first parts)
args (into [] (rest parts))
clauses (partition 2 2 body)]
`(case ~cmd
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args)]) clauses))))
(defn mysum [a b]
(+ (Integer. a) (Integer. b)))
(parse-cmd "SET 1 1" "SET" "GET" println)
2
This works well when cmd is a string, however with a var:
(def cmd "SET 1 1")
(parse-cmd cmd "SET" "GET" println)
I get ClassCastException clojure.lang.Symbol cannot be cast to java.lang.CharSequenceq clojure.string/split (string.clj:222)
I guess I should prevent the evaluation of the let too, but I can't make it work:
(defmacro parse-cmd [command & body]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))
clauses# (partition 2 2 ~body)]
(case cmd#
(mapcat (fn [c#] [(nth c# 0) `(apply ~(nth c# 1) args#)]) clauses#))))
With this definition I get:
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn kvstore.replication/eval12098 (form-init7453673077215360561.clj:1)
let's macroexpand this (for your second macro)
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
it expands to:
(let [parts__31433__auto__ (str/split "SET 1 1" #" ")
cmd__31434__auto__ (first parts__31433__auto__)
args__31435__auto__ (into [] (rest parts__31433__auto__))
clauses__31436__auto__ (partition
2
2
("SET" mysum "GET" println))]
(case
cmd__31434__auto__
(mapcat
(fn [c__31437__auto__] [(nth c__31437__auto__ 0)
(seq
(concat
(list 'apply)
(list (nth c__31437__auto__ 1))
(list 'args__31432__auto__)))])
clauses__31436__auto__)))
there are two problems here:
1) you generate this code: ("SET" mysum "GET" println), which obviously causes your exception, because "SET" is not a function
2) you generate the wrong case expression, I see that you have forgotten to unquote-splice your mapcat
Let's try to fix this:
first of all unquote mapcat; then you can move clauses out of your generated let, because it can be totally done at compile-time:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))]
(case cmd#
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) args#)]) clauses)))))
now let's check the expansion:
(let [parts__31653__auto__ (str/split "SET 1 1" #" ")
cmd__31654__auto__ (first parts__31653__auto__)
args__31655__auto__ (into [] (rest parts__31653__auto__))]
(case
cmd__31654__auto__
"SET"
(apply mysum args__31652__auto__)
"GET"
(apply println args__31652__auto__)))
ok. looks better. let's try to run it:
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
we have another error now:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: args__31652__auto__ in this context, compiling:(*cider-repl ttask*:2893:12)
so expansion also shows us this:
args__31655__auto__ (into [] (rest parts__31653__auto__))
...
(apply mysum args__31652__auto__)
so there are different symbols for args# here. That's because the scope of the generated symbol name is one syntax-quote. So inner syntax-quote with apply generates the new one. You should use gensym to fix that:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
~args-sym (into [] (rest parts#))]
(case cmd#
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args-sym)]) clauses)))))
ok now it should work properly:
ttask.core> (parse-cmd "SET 1 1" "SET" mysum "GET" println)
2
ttask.core> (parse-cmd cmd "SET" mysum "GET" println)
2
great!
I would also recommend you to use destructuring in a mapcat function and quoted let, to make it more readable:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [[cmd# & ~args-sym] (str/split ~command #" ")]
(case cmd#
~#(mapcat (fn [[op fun]] [op `(apply ~fun ~args-sym)]) clauses)))))
But if it's not just an exercise in writing macros, you shouldn't use the macro for that, since you pass just string and function references here, so anyway you shall evaluate everything in runtime.
(defn parse-cmd-1 [command & body]
(let [[cmd & args] (str/split command #" ")
commands-map (apply hash-map body)]
(apply (commands-map cmd) args)))

Clojure say-hi with varargs

Input: "Michael" "Julia" "Joe" "Sam"
Output: Hi, Michael, Julia, Joe, and Sam. (pay attention to the commas and the word "and")
Input: nil
Output: Hi, world.
Here is my first attempt:
(defn say-hi [& name]
(print "Hi," name))
user> (say-hi "Michael")
Hi, (Michael)
nil
user> (say-hi "Michael" "Julia")
Hi, (Michael Julia)
nil
Question:
How to implement default: (no input, say "Hi World!")
How to get rid of the parents around names in output?
How to implement the commas separation and add the conjunction word "and"?
First off, Clojure supports multi-arity functions, so you could do something like this to achieve default behaviour:
(defn say-hi
([] (say-hi "World"))
([& names] ...))
Then, what you want is to take a seq and join all the strings it contains together, using ", " in between. The clojure.string namespaces contains lots of string manipulation functions, one of them being clojure.string/join:
(require '[clojure.string :as string])
(string/join ", " ["Michael", "Julia"])
;; => "Michael, Julia"
But the last element of the seq should be concatenated using " and " as a separator, so you'll end up with something like this:
(require '[clojure.string :as string])
(defn say-hi
([] (say-hi "World"))
([& names]
(if (next names)
(format "Hi, %s, and %s!"
(string/join ", " (butlast names))
(last names))
(format "Hi, %s!" (first names)))))
Note that you have to differentiate between the single- and multi-name cases and (next names) basically checks whether the seq contains more than one element. (You could achieve the same by adding another arity to the function.)
(say-hi)
;; => "Hi, World!"
(say-hi "Michael")
;; => "Hi, Michael!"
(say-hi "Michael" "Julia" "Joe" "Sam")
;; => "Hi, Michael, Julia, Joe, and Sam!"
You can use clojure.string/join:
(use '[clojure.string :only [join]])
(defn sentencify [& elems]
(->>
[(join ", " (butlast elems)) (last elems)]
(remove empty?)
(join " and ")))
(defn say-hi [& name]
(print "Hi," (if name
(sentencify name)
"World!")))
A concise solution:
(defn say-hi [& names]
(let [names (case (count names)
0 ["world"]
1 names
(concat (butlast names) (list (str "and " (last names)))))]
(->> names, (cons "Hi"), (interpose ", "), (apply str))))
(say-hi)
;"Hi, world"
(say-hi "Michael")
;"Hi, Michael"
(say-hi "Michael" "Julia" "Joe" "Sam")
;"Hi, Michael, Julia, Joe, and Sam"
For long lists of names, you would want to eschew count, last, and butlast, maybe by pouring names into a vector first.
To print (as the question does) rather than return the formatted string, append print to the final form:
(->> names, (cons "Hi"), (interpose ", "), (apply str), print)

How can I find the difference in 2 data sets?

If I have 2 pipe delimited files containing bookmark data, for example. How can I read in the data then determine the difference in the two sets of data?
Input Set #1: bookmarks.csv
2|www.cnn.com|News|This is CNN
3|www.msnbc.com|Search|
4|news.ycombinator.com|News|Tech News
5|bing.com|Search|The contender
Input Set #2: bookmarks2.csv
1|www.google.com|Search|The King of Search
2|www.cnn.com|News|This is CNN
3|www.msnbc.com|Search|New Comment
4|news.ycombinator.com|News|Tech News
Output
Id #1 is missing in set #1
Id #5 is missing in set #2
Id #3 is different:
->www.msnbc.com|Search|
->www.msnbc.com|Search|New Comment
(use '[clojure.contrib str-utils duck-streams pprint]
'[clojure set])
(defn read-bookmarks [filename]
(apply hash-map
(mapcat #(re-split #"\|" % 2)
(read-lines filename))))
(defn diff-bookmarks [filename1 filename2]
(let [f1 (read-bookmarks filename1)
f2 (read-bookmarks filename2)
k1 (set (keys f1))
k2 (set (keys f2))
missing-in-1 (difference k2 k1)
missing-in-2 (difference k1 k2)
present-but-different (filter #(not= (f1 %) (f2 %))
(intersection k1 k2))]
(cl-format nil "~{Id #~a is missing in set #1~%~}~{Id #~a is missing in set #2~%~}~{~{Id #~a is different~% -> ~a~% -> ~a~%~}~}"
missing-in-1
missing-in-2
(map #(list % (f1 %) (f2 %))
present-but-different))))
(print (diff-bookmarks "bookmarks.csv" "bookmarks2.csv"))
Here is my stab at a functional-ish approach to the problem:
Create 2 maps, one for each file
Find the missing items between the two maps, using dissoc
Find the different, but shared items between the two maps, using intersection and filter
Code
(ns diffset
(:use [clojure.contrib.duck-streams]
[clojure.set]))
(def file1 "bookmarks.csv")
(def file2 "bookmarks2.csv")
(defn split-record [line]
"split line into (id, bookmark)"
(map #(apply str %)
(split-with #(not (= % \|)) line)))
(defn map-from-file [f]
"create initial map from file f"
(with-open [r (reader f)]
(doall (apply hash-map (apply concat (map split-record
(line-seq r)))))))
(defn missing [x y]
"return seq of all ids in x that are not in y"
(keys (apply dissoc x (keys y))))
(defn different [x y]
"return seq of all ids that match but have different bookmark string"
(let [match-keys (intersection (set (keys x)) (set (keys y)))]
(filter #(not (= (get x %)
(get y %)))
match-keys)))
(defn diff [file1 file2]
"print out differences between two bookmark files"
(let [[s1 s2] (map map-from-file [file1 file2])]
(dorun (map #(println (format "Id #%s is missing in set #1" %))
(missing s2 s1)))
(dorun (map #(println (format "Id #%s is missing in set #2" %))
(missing s1 s2)))
(dorun (map #(println (format "Id #%s is different:" %) "\n"
" ->" (get s1 %) "\n"
" ->" (get s2 %)) (different s1 s2)))))
Result
user> (use 'diffset)
nil
user> (diff file1 file2)
Id #1 is missing in set #1
Id #5 is missing in set #2
Id #3 is different:
-> |www.msnbc.com|Search|
-> |www.msnbc.com|Search|New Comment
nil
split them with re regexp and make a set out of them with (apply set (re-seq ... ) then call (difference set1 set2) to find the things that are in set 1 and not set 2. reverse it to find this items in set 2 that are not in set one.
look at http://clojure.org/data_structures for more info on clojure sets.
put the first data in a dictionary (hashtable) with the id as key
the read the next data line by line, retrieve the id from the hash.
if the id is not in the hash, output: id missing in set 1
if the value in the has differs, output: id is different
store the id's in a second hashtable
then run through the keys of the first hashtable
check if they are also in the second hashtable. if not output: id is missing in set2