I have the following line in my code:
(spit path (prn-str job-data))
It does it's work well execpt for one thing, every item in the list are put between double-quotes...
( ":a" ":b" ":a" )
the expected result that I'd like to have
( :a :b :a )
How to get the expected result?
Thanks in advance!
What's happening
The issue isn't that the items are being put in double quotes per se but that they're strings (as opposed to the keywords you're expecting).
prn-str, which is ultimately based on pr, prints objects "in a way that objects can be read by the reader". This means strings are printed in double-quotes - otherwise the reader wouldn't be able to tell strings from symbols, or read strings with whitespace in them. See here for more information on Clojure's reader.
println and print, on the other hand, are intended to "produce output for human consumption" and do not put strings in double-quotes. This is why you're seeing the difference in output between prn-str and println.
You can verify this with class. If you try (-> job-data first class) the answer will be either java.lang.String or clojure.lang.Keyword.
Here are some examples demonstrating the different behaviors of the printing functions when used with keywords and strings:
(def str-job-data '(":a" ":b" ":c"))
(def key-job-data '(:a :b :c))
;; `println` prints both keywords and strings without quotes
(with-out-str (println str-job-data)) ;=> "(:a :b :c)\n"
(with-out-str (println key-job-data)) ;=> "(:a :b :c)\n"
;; `prn-str` prints the strings in quotes but the keywords without quotes
(prn-str str-job-data) ;=> "(\":a\" \":b\" \":c\")\n"
(prn-str key-job-data) ;=> "(:a :b :c)\n"
How to change it
Now for possible solutions. If you were expecting job-data to contain keywords then the right fix is most likely to modify job-data. However, I can't offer much guidance here without knowing more about how job-data is produced.
If for some reason you can't modify job-data (for instance, if it's produced by code you don't control) and you want to write keywords wherever it contains keyword-like strings then something like #maxthoursie's suggestion is probably your best bet. (You could hypothetically just switch to print or println but that could have undesirable effects on how other objects are printed).
(defn keyword-string->keyword [s]
(keyword (subs s 1)))
(spit path (prn-str (map keyword-string->keyword job-data)))
If job-data might contain objects other than keyword-like strings you could apply the function only when appropriate.
(defn convert-job-data [obj]
(if (and (string? obj)
(= (.charAt obj 0) \:))
(keyword-string->keyword obj)
obj))
(spit path (prn-str (map convert-job-data job-data)))
Of course, if the file you're writing is for human consumption anyway and all this business about the reader is irrelevant you could trivially make your own println-str:
(defn println-str [& more]
(with-out-str (apply println more)))
(spit path (println-str job-data))
I'm guessing job-data is not what you expect it to be.
user=> (prn-str '(:a :b :c))
"(:a :b :c)\n"
If you do have a list with strings that looks like keywords, and you would like to convert it to keywords, you could use something like
(map (comp keyword #(subs % 1)) '(":a" ":b" ":c"))
Which skips the : of each element, and then converts it to a keyword.
user=> (prn-str (map (comp keyword #(subs % 1)) '(":a" ":b" ":c")))
"(:a :b :c)\n"
Related
I've started to get some functional programming some weeks ago and I'm trying to perform a mapping from a list of maps to a list considering a specific key in clojure.
My list of maps looks like: '({:a "a1" :b "b1" :c "c1"} {:a "a2" :b "b2" :c "c2"} {:a "a3" :b "b3" :c "c3"})
And the output I'm trying to get is: '("b1" "b2" "b3").
I've tried the following:
(doseq [m maps]
(println (list (get m :b))))
And my output is a list of lists (what is expected as I'm creating a list for each iteration). So my question is, how can I reduce this to a single list?
Update
Just tried the following:
(let [x '()]
(doseq [m map]
(conj x (get m :b))))
However, it is still not working. I`m not getting the point as I was expecting to be appending the elements into a empty list
This is a very common pattern in production Clojure code so it's a good place to learn. In general check out the docs on sequences at https://clojure.org/reference/sequences and when faced with similar task, look to see which pattern best fits and explore functions in that group. In this case it's "Process each item of a seq to create a new seq" and the first item listed is map
your example might look like
(map :b my-data)
You have the right idea, but are using the wrong function. doseq is intended only for side effects and always returns nil. The function you are looking for is for, which takes a sequence as input and returns another sequence as output. I generally prefer for over the similar map as for allows you to name the loop variable:
(def data-list
[{:a "a1" :b "b1" :c "c1"}
{:a "a2" :b "b2" :c "c2"}
{:a "a3" :b "b3" :c "c3"}])
(let [result (vec (for [item data-list]
(:b item)))]
(println result) ; print result
result) ; return result from `let` expression
result => ["b1" "b2" "b3"]
If instead you do this:
(println
(doseq [item data-list]
(println (:b item))))
you can see the difference with doseq vs for:
b1 ; loop item #1
b2 ; loop item #2
b3 ; loop item #3
nil ; return value of doseq
Please see https://www.braveclojure.com/ for online details, and buy a good book (or 5) like Getting Clojure, etc.
(doseq [m maps]
(println (list (get m :b))))
In two short lines, you break several general rules of functional programming:
Pass data into a function as arguments, not as references to global
variables.
Don't print the results of computation. Return them as the value of
the function.
Avoid mechanisms such as doseq that work by side-effects.
Despite this, you were not too far from a solution. doseq is essentially a version of for that throws away its result. If we replace doseq with for, and get rid of the println and the list, we get
=> (for [m maps] (get m :b))
("b1" "b2" "b3")
But Arthur Ulfeldt's simple use of map is better.
I've defined a record with a bunch of fields--some of which are computed, some of which don't map directly to keys in the JSON data I'm ingesting. I'm writing a factory function for it, but I want to have sensible default/not-found values. Is there a better way that tacking on :or [field1 "" field2 "" field3 "" field4 ""...]? I could write a macro but I'd rather not if I don't have to.
There are three common idioms for implementing defaults in constructor functions.
:or destructoring
Example:
(defn make-creature [{:keys [type name], :or {type :human
name (str "unnamed-" (name type))}}]
;; ...
)
This is useful when you want to specify the defaults inline. As a bonus, it allows let style bindings in the :or map where the kvs are ordered according to the :keys vector.
Merging
Example:
(def default-creature-spec {:type :human})
(defn make-creature [spec]
(let [spec (merge default-creature-spec
spec)]
;; ....
))
This is useful when you want to define the defaults externally, generate them at runtime and/or reuse them elsewhere.
Simple or
Example:
(defn make-creature [{:keys [type name]}]
(let [type (or type :human)
name (or name (str "unnamed-" (name type)))]
;; ...
))
This is as useful as :or destructoring but only those defaults are evaluated that are actually needed, i. e. it should be used in cases where computing the default adds unwanted overhead. (I don't know why :or evaluates all defaults (as of Clojure 1.7), so this is a workaround).
If you really want the same default value for all the fields, and they really have to be different than nil, and you don't want to write them down again, then you can get the record fields by calling keys on an empty instance, and then construct a map with the default values merged with the actual values:
(defrecord MyFancyRecord [a b c d])
(def my-fancy-record-fields (keys (map->MyFancyRecord {})))
;=> (:a :b :c :d)
(def default-fancy-fields (zipmap my-fancy-record-fields (repeat "")))
(defn make-fancy-record [fields]
(map->MyFancyRecord (merge default-fancy-fields
fields)))
(make-fancy-record {})
;=> {:a "", :b "", :c "", :d ""}
(make-fancy-record {:a 1})
;=> {:a 1, :b "", :c "", :d ""}
To get the list of record fields you could also use the static method getBasis on your record class:
(def my-fancy-record-fields (map keyword (MyFancyRecord/getBasis)))
(getBasis is not part of the public records api so there are no guarantees it won't be removed in future clojure versions. Right now it's available in both clojure and clojurescript, it's usage is explained in "Clojure programming by Chas Emerick, Brian Carper, Christophe Grand" and it's also mentioned in this thread during a discussion about how to get the keys from a record. So, it's up to you to decide if it's a good idea to use it)
I want to get following results when I evaluate edit-url and (edit-url 1).
edit-url --> "/articles/:id/edit"
(edit-url 1) --> "/articles/1/edit"
Is it possible to define such a Var or something?
Now, I use following function, but I don't want to write (edit-url) to get const string.
(defn edit-url
([] "/articles/:id/edit")
([id] (str "/articles/" id "/edit")))
Thanks in advance.
If those behaviors are exactly what you want, print-method and tagged literals may be used to imitate them.
(defrecord Path [path]
clojure.lang.IFn
(invoke [this n]
(clojure.string/replace path ":id" (str n))))
(defmethod print-method Path [o ^java.io.Writer w]
(.write w (str "#path\"" (:path o) "\"")))
(set! *data-readers* (assoc *data-readers* 'path ->Path))
(comment
user=> (def p #path"/articles/:id/edit")
#'user/p
user=> p
#path"/articles/:id/edit"
user=> (p 1)
"/articles/1/edit"
user=>
)
edit-url will either have the value of an immutable string or function. Not both.
The problem will fade when you write a function with better abstraction that takes a string and a map of keywords to replace with words. It should work like this
(generate-url "/articles/:id/edit" {:id 1})
Clojure is a "Lisp 1" which means that is has a single namespace for all symbols, including both data scalars and functions. What you have written shows the functionally of both a string and a function but for a single name, which you can do in Common Lisp but not Clojure (not that a "Lisp 2" has its own inconveniences as well).
In general this type of "problem" is a non issue if you organize your vars better. Why not just make edit-url a function with variable arity? Without arguments it returns something, with arguments it returns something else. Really the possibilities are endless, even more so when you consider making a macro instead of a function (not that I'm advocating that).
How to make clojure to count '() as nil?
For example:
How to make something like
(if '() :true :false)
;to be
:false
;Or easier
(my-fun/macro/namespace/... (if '() :true :false))
:false
And not just if. In every way.
(= nil '()) or (my-something (= nil '()))
true
And every code to be (= '() nil) save.
(something (+ 1 (if (= nil '()) 1 2)))
2
I was thinking about some kind of regural expression. Which will look on code and replace '() by nil, but there are some things like (rest '(1)) and many others which are '() and I am not sure how to handle it.
I was told that macros allow you to build your own languages. I want to try it by changing clojure. So this is much about "How clojure works and how to change it?" than "I really need it to for my work."
Thank you for help.
'() just isn't the same thing as nil - why would you want it do be?
What you might be looking for though is the seq function, which returns nil if given an empty collection:
(seq [1 2 3])
=> (1 2 3)
(seq [])
=> nil
(seq '())
=> nil
seq is therefore often used to test for "emptiness", with idioms like:
(if (seq coll)
(do-something-with coll)
(get-empty-result))
You say you would like to change Clojure using the macros. Presently, as far as I know, this is not something you could do with the "regular" macro system (terminology fix anyone?). What you would really need (I think) is a reader macro. Things I have seen online (here, for example) seem to say that there exists something like reader macros in Clojure 1.4--but I have no familiarity with this because I really like using clooj as my IDE, and it currently is not using Clojure 1.4. Maybe somebody else has better info on this "extensible reader" magic.
Regardless, I don't really like the idea of changing the language in that way, and I think there is a potentially very good alternative: namely, the Clojure function not-empty.
This function takes any collection and either returns that collection as is, or returns nil if that collection is empty. This means that anywhere you will want () to return nil, you should wrap it not-empty. This answer is very similar to mikera's answer above, except that you don't have to convert your collections to sequences (which can be nice).
Both using seq and not-empty are pretty silly in cases where you have a "hand-written" collection. After all, if you are writing it by hand (or rather, typing it manually), then you are going to know for sure whether or not it is empty. The cases in which this is useful is when you have an expression or a symbol that returns a collection, and you do not know whether the returned collection will be empty or not.
Example:
=> (if-let [c (not-empty (take (rand-int 5) [:a :b :c :d]))]
(println c)
(println "Twas empty"))
;//80% of the time, this will print some non-empty sub-list of [:a :b :c :d]
;//The other 20% of the time, this will return...
Twas empty
=> nil
What about empty? ? It's the most expressive.
(if (empty? '())
:true
:false)
You can override macros and functions. For instance:
(defn classic-lisp [arg]
(if (seq? arg) (seq arg) arg))
(defn = [& args]
(apply clojure.core/= (map classic-lisp args)))
(defmacro when [cond & args]
`(when (classic-lisp ~cond) ~#args))
Unfortunately, you can't override if, as it is a special form and not a macro. You will have to wrap your code with another macro.
Let's make an if* macro to be an if with common-lisp behavior:
(defmacro if* [cond & args]
`(if (classic-lisp ~cond) ~#args)
With this, we can replace all ifs with if*s:
(use 'clojure.walk)
(defn replace-ifs [code]
(postwalk-replace '{if if*} (macroexpand-all code)))
(defmacro clojure-the-old-way [& body]
`(do ~#(map replace-ifs body)))
Now:
=> (clojure-the-old-way (if '() :true :false) )
:false
You should be able to load files and replace ifs in them too:
(defn read-clj-file [filename]
;; loads list of clojure expressions from file *filename*
(read-string (str "(" (slurp filename) ")")))
(defn load-clj-file-the-old-way [filename]
(doseq [line (replace-ifs (read-clj-file filename))] (eval line))
Note that I didn't test the code to load files and it might be incompatible with leiningen or namespaces. I believe it should work with overriden = though.
I have a bunch of functions that map to and from some codes defined by an external system:
(defn translate-from-ib-size-tick-field-code [val]
(condp = val
0 :bid-size
3 :ask-size
5 :last-size
8 :volume))
(defn translate-to-ib-size-tick-field-code [val]
(condp = val
:bid-size 0
:ask-size 3
:last-size 5
:volume 8))
I'd like to make a macro to remove the duplication:
#_ (translation-table size-tick-field-code
{:bid-size 0
:ask-size 3
:last-size 5
:volume 8})
I started the macro like this:
(defmacro translation-table [name & vals]
`(defn ~(symbol (str "translate-to-ib-" name)) [val#]
(get ~#vals val#)))
The resulting function body seems right, but the function name is wrong:
re-actor.conversions> (macroexpand `(translation-table monkey {:a 1 :b 2}))
(def translate-to-ib-re-actor.conversions/monkey
(.withMeta (clojure.core/fn translate-to-ib-re-actor.conversions/monkey
([val__10589__auto__]
(clojure.core/get {:a 1, :b 2} val__10589__auto__))) (.meta ...
I'd like the "translate-to-ib-" to appear as part of the function name, instead of a prefix to the namespace, as it turned out.
How can I do this with clojure macros? If I am just doing it wrong and shouldn't use macros for this for some reason, please do let me know, but I would also like to know how to create function names like this to just improve my understanding of clojure and macros in general. Thanks!
The macro issue is twofold:
1) You're using a backtick when quoting the form passed to macroexpand, which namespace-qualifies the symbols within:
`(translation-table monkey {:a 1 :b 2})
=> (foo.bar/translation-table foo.bar/monkey {:a 1, :b 2})
where foo.bar is whatever namespace you're in.
2) You're constructing the name of the defn item using the symbol name, which, when it is namespace-qualified, will stringify to "foo.bar/monkey". Here's a version that will work:
(defmacro translation-table [tname & vals]
`(defn ~(symbol (str "translate-to-ib-" (name tname))) [val#]
(get ~#vals val#)))
Notice that we're getting the name of tname without the namespace part, using the name function.
As for whether a macro is the right solution here, probably not :-) For a simple case like this, I might just use maps:
(def translate-from-ib-size-tick-field-code
{0 :bid-size
3 :ask-size
5 :last-size
8 :volume})
;; swap keys & vals
(def translate-to-ib-size-tick-field-code
(zipmap (vals translate-from-ib-size-tick-field-code)
(keys translate-from-ib-size-tick-field-code)))
(translate-from-ib-size-tick-field-code 0)
=> :bid-size
(translate-to-ib-size-tick-field-code :bid-size)
=> 0
If speed is of the essence, check out case.
Some unsolicited advice on a different point: (get ~#vals val#) is extremely suspicious. Your macro alleges to take any number of arguments, but if it gets more than two it will just do something that doesn't make any sense. Eg,
(translation-table metric {:feet :meters}
{:miles :kilometers}
{:pounds :kilograms})
aside from being a terrible translation table, expands to code that always throws an exception:
(defn translate-to-ib-metric [val]
(get {:feet :meters}
{:miles :kilometers}
{:pounds :kilograms}
val)))
get doesn't take that many arguments, of course, and it's not really what you meant anyway. The simplest fix would be to only permit two arguments:
(defmacro translation-table [name vals]
(... (get ~vals val#)))
But note that this means the value map gets reconstructed every time the function is called - problematic if it's expensive to compute, or has side effects. So if I were writing this as a macro (though see Justin's answer - why would you?), I would do it as:
(defmacro translation-table [name vals]
`(let [vals# ~vals]
(defn ~(symbol (str "translate-to-ib-" name)) [val#]
(get vals# val#))))