Reputation: 1188
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)
Upvotes: 1
Views: 131
Reputation: 4173
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.
Upvotes: 1
Reputation: 9266
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.
Upvotes: 3