I have this function which load correctly my namespace :
(defn load-module [module-name]
(use module-name)
)
And my "equivalent" macro that doesn't work :
(defmacro load-module-macro [module-name]
`(
(use '~module-name)
)
)
I don't understand the problem.
Moreover, I want to use this macro for load a module choose in configuration. In my config.clj I define a var with the namespace of my logger module which contains "save-data" function. Then I want to load the specified logger in my core program. So I can choose the logger to use directly in my configuration file (logger on disk, logger in database...). Is it the best way to do that ?
EDIT :
Error message
IllegalArgumentException Don't know how to create ISeq from: java.lang.Character clojure.lang.RT.seqFrom (RT.java:505)
No, in fact you don't want to use "use" directly in code at all. Use modifies the entire namespace it is called in and that could break your code in ways that are hardly predictable.
Instead what you should do is:
Implement a logging interface (Protocol), write a "meta-constructor" that dispatches whatever you set in config.clj as keyword. Code example
(defprotocol ILog
(save-data [this msg] "Logs message in msg."))
(defn create-file-log
"Returns an object implementing ILog, opens and flushes java.io.File file."
[file]
(let [f ... ;; create file writer here
]
(reify ILog
(save-data [this msg] ;; Write code that writes data to file here
))))
;; create other implementations like database here or elsewhere
(defn create-log
"Creates a log of of the type passed in type-kw."
[type-kw]
(case type-kw
:file (create-file-log "./app-log.txt")
;; other types
))
Now you would simply invoke create-log with whatever keyword is set in your config file and pass the returned object around to functions that need to do logging. Obviously, you could also def it as a global object but I don't recommend to do that.
Eventually you don't just want to set a keyword (type-kw) for the desired logging method in your config, but also other parameters like the file-name or a database uri so that you can pass something like
{:log-method :file
:data {:fname "app-log.txt"}}
or
{:log-method :db
:data {:uri "....
...to your create-log function that uses this structure to get the parameters for the reify constructors create-file-log, create-db-log, etc.
EDIT:
Because you don't like the switch statement, here is how to do it with multi-methods:
(defmulti create-log :logging-method)
(defmethod create-log :file
[arg-map]
(let [file (java.io.File. (:fname arg-map))]
(if (.exists file)
...
Then you simply have an entry in your config.clj
{...
:log {:logging-method :file
:fname "./log-file.txt"}}
To create a new logging type all you have to do now is to imagine an argument map like the one above and a constructor method for create-log.
I think you can also use multimethods for this. It's clojure's other polymorphism strategy, and might work well for your use case as you're only looking to implement a single method (save-data).
;; set up a config map
(def config {:logger :db-logger)
;; set up the dispatch function to read the logger from the config map
(defmulti save-data (fn [] (:logger config))
;; define methods as required - database logging
(defmethod save-data :db-logger []
(println "Save to database"))
;; some other logging - can be in another file
(defmethod save-data :other-logger []
(println "Save to other thing"))
Note: I'm still quite new to Clojure - so I'm not sure if this is a 'proper' way to use multimethods. Most of the examples I've seen dispatch on the type of the arguments, not on a config setting. Any experts, please correct me if I've got the wrong idea.
Related
I was exploring Clojure java.jmx API Reference and trying the examples mentioned there e.g.
;; Can I serve my own beans? Sure, just drop a Clojure ref
;; into an instance of clojure.java.jmx.Bean, and the bean
;; will expose read-only attributes for every key/value pair
;; in the ref:
(jmx/register-mbean
(create-bean
(ref {:string-attribute "a-string"}))
"my.namespace:name=Value")
It works great, the bean's attribute value is visible in the console but it is read-only.
Is there a way to create a writeable bean(so that it is listed in "Operations" folder and manageable from console)?
It looks like the clojure.java.jmx code supports setAttribute.
(see (deftype Bean ...) in https://github.com/clojure/java.jmx/blob/master/src/main/clojure/clojure/java/jmx.clj
The easiest way seems to be just using an atom instead of a ref.
You could then have atom watchers to run some code if JMX changes it.
Maybe give that a try. I've forgotten most of JMX ;)
EDIT: Quickly tried it. It looks like the attribute is still read-only :(
I gotta look deeper. Nevertheless, the source code is pretty nice and hopefully easy to understand.
EDIT2:
The issue is with build-attribute-info which passes false to the writeable? flag in the `MBeanAttributeInfo.
You can monkey patch that:
(import 'java.jmx.MBeanAttributeInfo)
(require '[clojure.java.jmx :as jmx])
(defn build-my-attribute-info
"Construct an MBeanAttributeInfo. Normally called with a key/value pair from a Clojure map."
([attr-name attr-value]
(build-my-attribute-info
(name attr-name)
(.getName (class attr-value)) ;; you might have to map primitive types here
(name attr-name) true true false)) ;; false -> true compared to orig code
([name type desc readable? writable? is?] (println "Build info:" name type readable? writable?) (MBeanAttributeInfo. name type desc readable? writable? is? )))
;; the evil trick one should only use in tests, maybe
;; overwrite the original definition of build-attribute-info with yours
(with-redefs [jmx/build-attribute-info build-my-attribute-info]
(jmx/register-mbean (jmx/create-bean (atom {:foo "bar"})) "my.bean:name=Foo"))
;; write to it
(jmx/write! "my.bean:name=Foo" "foo" "hello world")
;; read it
(jmx/read "my.bean:name=Foo" "foo")
=> "hello world"
Now, unfortunately, Java Mission Control still can't change the value, but I'm not sure why. The MBean info is correct.
Must be a permission thing.
I have the following clojure code to initialize my config structure Config.
I noticed that the file is actually read when compiling the file, not at runtime.
For me, the config structure Config should be immutable, however, I do not want to have the configuration inside the JAR file.
How should I do this? Do I have to use an atom? It is okay if the application crashes if my.config is missing.
(def Config
(read-string (slurp "my.config")))
When you don't want it at compile time you have to wrap it in a function.
(defn def-my-conf []
(def Conf (blub)))
But you the cleaner way would be:
(declare Config)
(defn load-Config []
(alter-var-root (var Config) (blub)))
This function should be called inside your main.
EDIT: Of course an atom is also a solution!
Write a function for reading your config:
(defn read-config
[]
(read-string
(slurp "my.config")))
Then you can call this function from -main, and either 1) pass the config on to any functions that will need it, or 2) store it in a dynamic variable and let them read it directly:
(def ^:dynamic *config* nil)
(defn some-function-using-config
[]
(println *config*))
(defn -main
[]
(binding [*config* (read-config)]
(some-function-using-config)))
Which of the two to choose is a matter of taste and situation. With direct passing you make it explicit that a function is receiving config, with the dynamic variable you avoid having to include config as an argument to every single function you write, most of whom will just pass it on.
Both of these solutions work well for unit tests, since you can just rebind the dynamic variable to whatever config you want to use for each test.
TheQuickBrownFox had the answer, the file is read both at run-time, and a compile-time.
Fine for me! That is actually really cool!
I'm writing a small debugging library and I would like to let users choose how to display data structures. I was imagining that users could require it in this kind of way:
(ns some-user-namespace
(:require
[clojure.pprint]
[my.library :with-args {print-fn clojure.pprint/pprint}]))
Is something like this possible, and if not, how can I solve this problem?
It's not possible to do it this way. If you really to offer this kind of setup, you could provide a configuration function to be called by the user after the import:
(ns some-namespace
(:require [my.library]))
(my.library/print-with! clojure.pprint/pprint)
Ending function name with ! is an idiomatic way of indicating that it's causing some side effects.
In your library it could look like:
(ns my.library)
(def config (atom {:printer println}))
(defn print-with! [new-printer]
(swap! config assoc :printer new-printer))
(defn my-lib-print [foo]
((:printer #config) foo))
EDIT: For a solution that does not require global, mutable state you can use dynamic bindings.
Lib:
(ns my.library)
(def ^:dynamic *printer* println)
(defn my-lib-print [foo]
(*printer* foo))
Usage:
(binding [my.library/*printer* clojure.pprint/pprint]
(my.library/my-lib-print {:hello "World"}))
These are the only two ways for some kind of external, contextual configuration I can think of. The only alternative is pure higher order function:
(defn my-lib-print [printer foo]
(printer foo))
In the project I'm working on we often define custom defsomething-style macros for different purposes to hide boilerplate. One example is defhook which helps to define a hook handler for an event. Here's a simplified version of it (the actual version has more parameters and does some non-trivial things in defmethod, but that's irrelevant to my question):
(defmulti handle-hook
"This multimethod is called when an event was fired."
(fn [event context] event))
(defmacro defhook
"Define a hook for an event."
[event docstring & more]
`(let [body# (fn ~#more)]
(defmethod handle-hook ~event [event# context#]
(body# context#))))
(defhook "EntryDeleted"
"Hook called on entry deletion."
[context]
(log-deletion (:EntryID context)))
The main problem I have with this code is that defmethod does not support docstring, so I can't use the one for "EntryDeleted" in REPL or for automatic documentation generation. The last one is important for the project: there are defhooks and defhandlers that are exposed as external API and currently we have to maintain documentation separately (and manually).
So the simplest question is "how to attach docstring to a defmethod"?.
And the deeper one would be "how to attach/generate documentation for custom defsomething macros?"
If some of the existing tools for documentation generation supported this feature it would be great! Yet, neither of Marginalia, Codox or Autodoc seem to support something like that.
How to attach/generate documentation for custom defsomething macros?
Since docstrings are attached to vars, you'd generally have defsomething macros expand into a more primitive def form, e.g. defn, def. Then you just arrange for your defsomething's docstring to be attached to the underlying var.
How to attach docstring to a defmethod?
This is a special case - defmethod is not defining a new var; it's calling a Java method on a Java object. On the other hand, defmulti does create a var. One idea would be to extend the multi-function's docstring with the dispatch value and associated description. For example,
(defn append-hook-doc! [event docstring]
(let [hook-doc (str event " - " docstring)]
(alter-meta! #'handle-hook
(fn [m]
(update-in m [:doc] #(str % "\n\t" hook-doc))))))
...
(doc handle-hook)
-------------------------
user/handle-hook
This multimethod is called when an event was fired.
EntryDeleted - Hook called on entry deletion.
As the ! indicates, this form has a side-effect: multiple evaluations of a defining form that calls this will result in duplicate lines in #'handle-hook's docstring. You might avoid this by stashing some extra metadata in #'handle-hook to use as a marker for whether or not the doc has already been appended. Alternatively, you might stash the docstrings elsewhere and patch it all together in some auxiliary step, e.g. by delaying the expansion of (defmulti handle-hook ... until you have all the docstrings (although, this breaks the open extension of multi-methods wrt docstrings).
I have a Clojure program that I build as a JAR file using Maven. Embedded in the JAR Manifest is a build-version number, including the build timestamp.
I can easily read this at runtime from the JAR Manifest using the following code:
(defn set-version
"Set the version variable to the build number."
[]
(def version
(-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
(.getCodeSource)
(.getLocation))
"!/META-INF/MANIFEST.MF")
(URL.)
(.openStream)
(Manifest.)
(.. getMainAttributes)
(.getValue "Build-number"))))
but I've been told that it is bad karma to use def inside defn.
What is the Clojure-idiomatic way to set a constant at runtime? I obviously do not have the build-version information to embed in my code as a def, but I would like it set once (and for all) from the main function when the program starts. It should then be available as a def to the rest of the running code.
UPDATE: BTW, Clojure has to be one of the coolest languages I have come across in quite a while. Kudos to Rich Hickey!
I still think the cleanest way is to use alter-var-root in the main method of your application.
(declare version)
(defn -main
[& args]
(alter-var-root #'version (constantly (-> ...)))
(do-stuff))
It declares the Var at compile time, sets its root value at runtime once, doesn't require deref and is not bound to the main thread. You didn't respond to this suggestion in your previous question. Did you try this approach?
You could use dynamic binding.
(declare *version*)
(defn start-my-program []
(binding [*version* (read-version-from-file)]
(main))
Now main and every function it calls will see the value of *version*.
While kotarak's solution works very well, here is an alternative approach: turn your code into a memoized function that returns the version. Like so:
(def get-version
(memoize
(fn []
(-> (str "jar:" (-> my.ns.name (.getProtectionDomain)
(.getCodeSource)
(.getLocation))
"!/META-INF/MANIFEST.MF")
(URL.)
(.openStream)
(Manifest.)
(.. getMainAttributes)
(.getValue "Build-number")))))
I hope i dont miss something this time.
If version is a constant, it's going to be defined one time and is not going to be changed you can simple remove the defn and keep the (def version ... ) alone. I suppose you dont want this for some reason.
If you want to change global variables in a fn i think the more idiomatic way is to use some of concurrency constructions to store the data and access and change it in a secure way
For example:
(def *version* (atom ""))
(defn set-version! [] (swap! *version* ...))