Clojure hiccup vanishing key namespaces - clojure

I'm printing out a map's key values to html, and the key namespaces are vanishing, which I don't want.
layout below calls hiccup's html5 to render:
(layout (str "Path " (:path/title path))
[:h1 "Title: " (:title path) slug]
[:p (str path)] ; prints "{:db/id 17592186045542, :path/title "sdf"}"
(println (keys path)) ; prints in terminal "(:db/id :path/title)"
[:p (keys path)] ; prints "idtitle"
(for [[k v] path] [:p k " " v]) ; prints "id 17592186045542" /n "title sdf"
(map (fn [[k v]] [:p k " " v]) path)))) ; same as above
In both (keys path), and the for & map calls, the ":db/" and ":path/" namespaces of the keys are not rendered. Why?

I suppose the keys are being implicitly named, unlike the good cases where you explicitly use str on them.
Maybe you should use
[:p (str k) " " (str v)]
Or simply:
[:p (str/join " " [k v])]

Related

Function for applying to a user with a various number of data fields

The question was born when I was practicing an Observer topic in a tutorial
I am trying to apply the function to the user but cannot use user's data fields like name, surname.
Let's say that the user may have various number of data fields so we must use & args argument. My code that does not work:
(ns observer.core)
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(apply
(fn [& args] (println (str "I am " (:name args) " " (:surname args) ".")))
user)
(apply
(fn [& args] (println (str "My sister is " (:name args) " " (:surname args) ".")))
user2)
The output:
I am .
My sister is .
observer.core>
How to fix it regarding that the apply function must be used?
apply converts a map to a seq, i.e.
{:name "Alan" :surname "Smith" :alias "Mike"} becomes ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
You could put it back into a map, if that is what you need:
(let [user {:name "Alan" :surname "Smith" :alias "Mike"}]
(apply
(fn [& args]
(let [args (into {} args)]
(println (str "I am " (:name args) " " (:surname args) "."))))
user))
but this looks a bit of a stretch to me. I believe the solution could have been better if I knew how this function is supposed to be used.
Usually there are two types of functions: (fn :text "some" :row 25) and (fn {:text "some" :row 25}).
In the spirit of learning:
Check out Clojure - Cheatsheet.
10 years with Clojure, and I still use it daily.
(apply some-func (list x y z)) becomes (some-func x y z), because apply assumes that the second argument is a list (which it then unpacks).
And what you are currently doing is collecting all the arguments back into a list called args
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(prn 'ARGS args) ;; lets see what is actually in args
(println (str "I am " (:name args) " " (:surname args) ".")))
user)
;; -> ARGS ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
;; -> I am .
And the outut is as #akond says.
You could, of course, put 'user' in a vector (or list), but then don't use '&' to collect everything back into a list, (which you would then have to pick stuff out of again):
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [args]
(prn 'ARGS args)
(println (str "I am " (:name args) " " (:surname args) ".")))
[user])
That would give you the output you expected. But this is a bit strange, perhaps, but certainly viable if you must use apply and you can control the "list" part of the argument.
So, #akond's solution is simple and clean.
And augmenting it with Clojure "destructing":
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(let [{:keys [name surname alias]} (into {} args)]
(println (str "I am " name " " surname "." (when alias (str " But call me " alias "!"))))))
user)
I believe you intended to do something like this:
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(defn fn-1
[item]
(println (str "I am " (:name item) " " (:surname item) ".")) )
(defn fn-2
[item]
(println (str "My sister is " (:name item) " " (:surname item) ".")))
(fn-1 user)
(fn-2 user2)
with result:
I am Alan Smith.
My sister is Jane Smith.
One has to wrap a user object or the map by a list.
(ns observer.core)
(defrecord Person [name balance])
(def user (Person. "Alan" 150.34))
(def user2 {:name "Adam" :balance 629.74})
(def observers (atom #{}))
(swap! observers conj (fn [l] (println (str "2. " (:name l)))))
(swap! observers conj (fn [l] (println (str "1. " (:balance l)))))
(println "user")
(vec (map #(apply % (list user)) #observers))
(println "\nuser2")
(vec (map #(apply % (list user2)) #observers))
Output
user
1. 150.34
2. Alan
user2
1. 629.74
2. Adam
observer.core>

Anonymous function with rest parameter throwing NullPointerException

Here's the function
(#(
(println (str "first: " %1))
(println (str "second: " %2))
(println (str "rest: " (clojure.string/join ", " %&))))
"f" "s" "x" "y" "z")
When running this in cider I get the desired result but at the end I see that I am also getting a NullPointerException.
It seems that this form of anonymous function has some issues with destructuring.
Because, when I try the following form of anonymous function, it works.
((fn [f s & rest]
(println (str "first: " f))
(println (str "second: " s))
(println (str (clojure.string/join ", " rest))))
"f" "s" "x" "y" "z")
Can someone explain why is this happening ?
You need a do:
(#(do
(println (str "first: " %1))
(println (str "second: " %2))
(println (str "rest: " (clojure.string/join ", " %&))))
"f" "s" "x" "y" "z")
Without the do, you're trying to invoke the result of the first println, (i.e. nil) on the remaining elements of the list. fn has an implicit do.
For a minimal case, compare ((println)) and (do (println))

How do I join string set into a single string with positions prepended?

Assume I have this
(def base ["one" "two" "three"])
I want to convert it to:
1. one
2. two
3. three
(aka 1. one \n2. two \n3. three)
with join, I am not sure I can append a counter before joining:
(clojure.string/join " \n" base)
=> "one \ntwo \nthree"
and with doseq or similar, plus an atom, I do get individual strings but then will have to concatenate later on, something like
(def base ["one" "two" "three"])
(def pos (atom 0))
(defn add-pos
[base]
(for [b base]
(do
(swap! pos inc)
(str #pos ". " b))))
(let [pos-base (add-pos base)]
(clojure.string/join " \n" pos-base))
=> "1. one \n2. two \n3. three"
While it works, I don't know if using an atom with a for statement is he best way to do this, it doesn't look very clojure-esque.
Is there a better way to do this please?
That's a job for keep-indexed:
user> (keep-indexed #(str (inc %1) ". " %2) ["one" "two" "three"])
("1. one" "2. two" "3. three")
user> (clojure.string/join "\n"
(keep-indexed
#(str (inc %1) ". " %2)
["one" "two" "three"]))
"1. one\n2. two\n3. three"
A minor alternative to schaueho's keep-indexed would be map-indexed (spotting a pattern?)
(def base ["one" "two" "three"])
(defn numbered-list [s]
(->> s
(map-indexed #(str (inc %1) ". " %2))
(interpose \newline)
(apply str)))
(numbered-list base) ; => "1. one\n2. two\n3. three"
Clearly a job for interleave.
(->> (interleave (rest (range)) (repeat ". ") base (repeat " \n"))
(apply str))
;-> "1. one \n2. two \n3. three \n"

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)

macro how deal with sql like fn use map

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 );"