How can I add a var to a clojure namespace, i.e. so it can be retrieved later through a call to ns-interns?
This is not (intern ns sym ...), because intern creates a var or reuses whatever var sym is bound to. This distinction is important when other namespaces require ns and hold references to the vars in ns. What I am asking for is the opposite of ns-unmap.
Can't you just use def?:
def
Creates and interns or locates a global var with the name of symbol and a
namespace of the value of the current namespace (*ns*).
and in code:
(def alpha)
(def beta :boo)
(intern 'tst.demo.core 'gamma :charlie)
(ns-interns 'tst.demo.core)
=> {alpha #'tst.demo.core/alpha,
beta #'tst.demo.core/beta
gamma #'tst.demo.core/gamma}
I'm not sure what you're after here...? They all seem the same.
Background: ns-interns vs ns-map
ns-interns returns a map of Vars originally interned in the given namespace. There's no way to take a Var from one namespace and make it show up in another namespace's output of ns-interns. Thus if you want to make a Var show up here, you'll need to use intern or def.
In contrast, ns-map is the more fundamental primitive that returns a potentially larger map that includes "own interns", but also Vars from other namespaces brought in through :require :refer and imported Java classes.
(It is more fundamental because ns-interns works by calling ns-map and filtering it so that only Vars originally interned in the given namespace are kept. This is possible because Vars keep track of the namespace they where created in, if any, and any originally assigned name.1)
The map that ns-map returns is the one that ns-unmap removes mappings from. Note that ns-map and ns-unmap make no distinction between "own Vars" and Vars from other namespaces. Var entries in this map are created when you use intern or def, but also when Vars from other namespaces are mapped in. If the latter is what you want to do, there are several ways to go about it.
Adding Vars from other namespaces to a given namespace's ns-map
The standard way is to use :require :refer … :rename … or :use :only … :rename … in the ns form or clojure.core/refer :only … :rename … at the REPL:
user=> (refer 'clojure.string :only '[lower-case] :rename '{lower-case lc})
nil
user=> (lc "ASDF")
"asdf"
NB. the argument following :only is a collection of non-namespaced symbols that name the Vars you want to pull in in their own namespace – the original names – and the :rename map has the original names in the key position and your replacement names in the value position.
If you're happy to accept the original name, you can leave out :rename.
It's also possible to call clojure.core/require or clojure.core/use, which will ensure that the namespace is loaded and then refer the Vars you need. The syntax is as in an ns form – see below – except the collections of symbols following :only and :rename need to be quoted as in the refer call above.
In an ns form you'd say
(:require [clojure.string :refer [lower-case] :rename {lower-case lc}])
or
(:use [clojure.string :only [lower-case] :rename {lower-case lc}])
:require tends to be preferred these days as a matter of style.
1 For completeness, it is possible to create Vars that don't consider themselves to belong to any namespace or to have any particular name, but there's no public API for adding those to any namespace map as of right now; in fact their presence in a namespace map would cause certain weird behaviours.
Related
TL;DR: Is the following a good pattern for a library?
(def ^{:dynamic true} *var*)
(defn my-fn [{:keys [var]}]
(do-smth (or var *var*)))
--
Say I want to write a sentiment analysis library.
Is it good design in get-sentiment fn to accept optional sentiment labels but provide default one as dynamic var?
(def ^:dynamic *sentiment-level-labels*
["Very Negative" "Negative" "Neutral" "Positive" "Very Positive"])
;;...
(defn get-sentiment-scores
"Takes text and gives back a 0 to 4 sentiment score for each sentences."
[text]
;;...)
(defn get-sentiment
"Gives back a sentiment map with sentences scores,
average score, rounded score and labeled score.
Can accepts custom sentiment level labels under :labels opt."
[text & {:keys [labels]}]
(let [scores (get-sentiment-scores text)
average-score (get-average scores)
rounded-score (Math/round average-score)
label (get (or labels *sentiment-level-labels*) rounded-score)]
{:scores scores
:average-score average-score
:rounded-score rounded-score
:label label}))
Clojure library coding standards official page says:
If you present an interface that implicitly passes a parameter via
dynamic binding (e.g. db in sql), also provide an identical interface
but with the parameter passed explicitly.
https://dev.clojure.org/display/community/Library+Coding+Standards
In my example, I provided only one interface but with opt argument.
Is this okay? Are there better ways to handle this?
Thank you!
Dynamic vars are full or pitfalls. They push your API code towards implicit environmental coupling, and often force your calling code to add a lot of (binding ...) clauses, which kind of defeats the purpose of concision for using Dynamic vars in the first place. They also lead to tricky edge cases if control is passed from one thread to another.
In your case, I would recommend simply passing the labels in a params map argument:
(def default-sentiment-level-labels
["Very Negative" "Negative" "Neutral" "Positive" "Very Positive"])
(defn get-sentiment
"Gives back a sentiment map with sentences scores,
average score, rounded score and labeled score.
Can accepts custom sentiment level labels under :labels opt."
[text {:as params, :keys [labels] :or {labels default-sentiment-labels}}]
...))
Note that the usage of a map can be interesting, because a map is opaque to intermediaries: you can have various components of your algorithm read only from the params map the keys that concern them.
I what the idiomatic way of handling optional query parameters in Compojure and assign undefined query parameters to a default value.
I've tried this (which perhaps obviously doesn't work):
(GET "/something/:id" [id q :<< as-int :or {q (System/currentTimeMillis)}]
....)
I'd like this route to match both:
curl /something/123
and
curl /something/123?q=234542345
(Note a similar question has been posted here but it uses Swagger)
There are lots of options you could take. This is one I might choose.
(GET "/something/:id" req
(let [{:keys [id q] :or {q (System/currentTimeMillis)}} (:params req)]
,,,))
Ultimately, though, which one I chose would depend on whatever led to the most readable (a subjective measurement, but that's the way it goes) code.
I am trying to 'purify' some of my Clojure functions. I would like to get to a point where all my side-effecting code is explicitly declared in one function. It's easy to get some data at the start and to write it to a db at the end and just have a pure function transforming in between. However, the usual situation is that the transformation function requires another DB read somewhere in the middle of the logic:
(defn transform-users
[users]
(let [ids (map :id users)
profiles (db/read :profiles ids)]
(profiles->something profiles)))
(->> (db/read :users)
(transform-users)
(db/write :something)
Obviously this is a very simple example but the point is, how do I get the side-effecting db/read function out of there, how can I make transform-users pure (and as a benefit, easily testable)?
One thing you could do here would be a dependency-injection-like approach of supplying the (potentially) side-effectful function as an optional parameter, e.g.:
(defn transform-users
[users & {:keys [ids->profiles]
:or {ids->profiles #(db/read :profiles %)}]
(let [ids (map :id users)
profiles (ids->profiles ids)]
(profiles->something profiles)))
This should be easily testable since you can mock the injected functions without a lot of effort. And as a bonus, by supplying the default value, you're documenting what you're expecting and making the function convenient to call.
Why couple the reading of the profiles with transforming profiles?
(->> (db/read :users)
(map :id)
(db/read :profiles)
(profile->something)
(db/write :something)
(This also exposes the fact that you are doing two round trips to the db. Where is db/read :user-profiles ?)
(->> (db/read :user-profiles)
(profile->something)
(db/write :something)
or perhaps:
(->> (read-profiles-from-users)
(profile->something)
(db/write :something)
I am new to Clojure and am just trying to build some sample apps to get used to the syntax. I noticed the following order dependency behaviour.
I created a project called timex to calculate the time in weeks between two dates. I am using the clj-time functions for the date difference calculations.
If my core.clj looks as follows:
(ns timex.core
(:gen-class))
(defn -main
"Calculate weeks between dates."
[& args]
dp
)
(require '[clj-time.core :as t])
(def d2 (t/date-time 1989 01 07))
(def dw (t/in-weeks (t/interval d2 (t/now))))
(def dp (str "The number of weeks between Jan 7, 1989 and now is " dw "!"))
**
If I run lein repl I get the following error:
#CompilerException java.lang.RuntimeException: Unable to resolve symbol: dp in this context, compiling:(timex/core.clj:4:1)
But if I re-order the lines in the file and put the def's and the require statement before the main as such
(ns timex.core
(:gen-class))
(require '[clj-time.core :as t])
(def d2 (t/date-time 1989 01 07))
(def dw (t/in-weeks (t/interval d2 (t/now))))
(def dp (str "The number of weeks between Jan 7, 1989 and now is " dw "!"))
(defn -main
"Calculate weeks between dates."
[& args]
dp
)
**
Then when I run lein repl and the invoke the (-main) function, I get:
timex.core=> (-main)
"The number of weeks between Jan 7, 1989 and now is 1341!"
Is this apparent order-dependency normal or am I doing something wrong? If the latter, then I would appreciate any advice or documentation I should review. Thanks.
The unit of compilation in Clojure is the s-expression as opposed to a whole file like many other languages. When you load a file that file file is evaluated from top to bottom in a single pass. vars (what is created by calls to def) must be created above where they are used, or you can use declare if you need mutual recursion though this is rare.
It's worth spending some time getting used to the difference between compiling a file in the traditional sense, and loading a file in the lisp sense because it is fundamental to the macro system and many other aspects of the language. When you require a file from a namespace, or call load-file from the repl the clojure compiler is invoked and repeatedly reads, macro-expands, then evaluates each from in the file starting at the top. The first line tels it what namespace to define things in which is why the ns expression comes first. Then further forms define things in that namespace. If you then load the file again it does nothing more than read the file from the top all over again. It does not clear the namespace or any other magic, so if you evaluate the file, then change the order and evaluate it again it can continue to work because the required functions are already defined in the namespace (as stored in memeory) when the second pass is run. When getting used to this it helps to run lein check often to make sure things are well ordered.
PS: the vast majority of the time the call to require goes in the ns form at the top of the file.
I am working through the first edition of this book and while I enjoy it, some of the examples given seem out-dated. I would give up and find another book to learn from, but I am really interested in what the author is talking about and want to make the examples work for myself, so I am trying to update them as I go along.
The following code is a map/reduce approach to analyzing text that depends on clojure.contrib. I have tried changing the .split function to re-seq with #"\w+", used line-seq instead of read-lines, and changed the .toLowerCase to string/lower-case. I tried to follow my problems to the source code and read the docs thoroughly to learn that the read-lines function closes after you consume the entire sequence and that line-seq returns a lazy sequence of strings, implementing java.io.BufferedReader. The most helpful thing for my problem was post about how to read files after clojure 1.3. Even still, I can't get it to work.
So here's my question: What dependencies and/or functions do I need to change in the following code to make it contemporary, reliable, idiomatic Clojure?
First namespace:
(ns chapter-data.word-count-1
(:use clojure.contrib.io
clojure.contrib.seq-utils))
(defn parse-line [line]
(let [tokens (.split (.toLowerCase line) " ")]
(map #(vector % 1) tokens)))
(defn combine [mapped]
(->> (apply concat mapped)
(group-by first)
(map (fn [[k v]]
{k (map second v)}))
(apply merge-with conj)))
(defn map-reduce [mapper reducer args-seq]
(->> (map mapper args-seq)
(combine)
(reducer)))
(defn sum [[k v]]
{k (apply + v)})
(defn reduce-parsed-lines [collected-values]
(apply merge (map sum collected-values)))
(defn word-frequency [filename]
(map-reduce parse-line reduce-parsed-lines (read-lines filename)))
Second namespace:
(ns chapter-data.average-line-length
(:use rabbit-x.data-anal
clojure.contrib.io))
(def IGNORE "_")
(defn parse-line [line]
(let [tokens (.split (.toLowerCase line) " ")]
[[IGNORE (count tokens)]]))
(defn average [numbers]
(/ (apply + numbers)
(count numbers)))
(defn reducer [combined]
(average (val (first combined))))
(defn average-line-length [filename]
(map-reduce parse-line reducer (read-lines filename)))
But when I compile and run it in light table I get a bevy of errors:
1) In the word-count-1 namespace I get this when I try to reload the ns function after editing:
java.lang.IllegalStateException: spit already refers to: #'clojure.contrib.io/spit in namespace: chapter-data.word-count-1
2) In the average-line-length namespace I get similar name collision errors under the same circumstances:
clojure.lang.Compiler$CompilerException: java.lang.IllegalStateException: parse-line already refers to: #'chapter-data.word-count-1/parse-line in namespace: chapter-data.average-line-length, compiling:(/Users/.../average-line-length.clj:7:1)
3) Oddly, when I quit and restart light table, copy and paste the code directly into the files (replacing what's there) and call instances of their top level functions the word-count-1 namespace runs fine, giving me the number of occurrences of certain words in the test.txt file but the average-line-length name-space gives me this:
"Warning: *default-encoding* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *default-encoding* or change the name. (clojure/contrib/io.clj:73)...
4) At this point when I call the word-frequency functions of the first namespace it returns nil instead of the number of word occurrences and when I call the average-line-length function of the second namespace it returns
java.lang.NullPointerException: null
core.clj:1502 clojure.core/val
As far as I can tell, clojure.contrib.io and clojure.contrib.seq-utils are no longer updated, and in fact they may be conflicting with clojure.core functions like spit. I would recommend taking out those dependencies and seeing if you can do this using only core functions. spit should just work -- the error that you're getting is caused by useing clojure.contrib.io, which contains its own spit function, which looks to be roughly equivalent; perhaps the current version in clojure.core is a "new and improved" version of clojure.contrib.io/spit.
Your problem with the parse-line function looks to be caused by the fact that you've defined two functions with the same name, in two different namespaces. The namespaces don't depend on one another, but you can still run into a conflict if you load both namespaces in a REPL. If you only need to use one at a time, try using one of them, and then when you want to use the other one, make sure you do a (remove-ns name-of-first-ns) first to free up the vars so there is no conflict. Alternatively, you could make parse-line a private function in each namespace, by changing (defn parse-line ... to (defn- parse-line ....
EDIT: If you still need any functions that were in clojure.contrib.io or clojure.contrib.seq-utils that aren't available in core or elsewhere, you can always copy the source over into your namespace. See clojure.contrib.io and clojure.contrib.seq-utils on github.