I have a macro defined like so in macros.cljh file:
(defmacro db-event [event-key params & body]
`(do
(re-frame.core/reg-event-db ~event-key
(fn [~'db [_# ~#params]]
(deep-merge ~'db ~#body)))
(defn ~(symbol event-key) ~params (re-frame.core/dispatch [~event-key ~#params]))))
which references a function deep-merge. This macro is defined in the namespace myapp.macros. Yet when I use the macro in a cljs file, I get the error:
Use of undeclared Var myapp.macros/deep-merge
even though deep-merge is also defined in the myapp.macros namespace
I have the macro required in macros.cljs file like so too:
(ns myapp.macros
(:require-macros [myapp.macros]))
I tried adding this to the cljs namespace declaration:
(:require [myapp.macros :refer [deep-merge]])
But that gives a circular dependency error. What am I doing wrong in refereeing the deep-merge function?
Macros in ClojureScript are more complicated, and must adhere to a 2-stage compilation process.
See these links for starters, then search out other blog postings & docs as well
https://clojurescript.org/about/differences#_macros
https://blog.fikesfarm.com/posts/2016-03-01-clojurescript-macro-sugar.html
In particular, the macro must be defined in a *.cljc or *.clj file, then used in the *.cljs file. You can see some examples in action in this template project:
https://github.com/cloojure/cljs-template
I just checked and it still works, although it uses the old version of Figwheel and has not yet been upgraded to Figwheel-Main (aka Figwheel 2.0).
You should be able to define the macro in the macros.clj file with deep-merge in macros.cljs. As you’ve seen, you can’t introduce circular dependencies. Your goal is to require the namespace, with the macros. :require-macros ensures that if you require the namespace, the macros are included. You should be able to use the macro by requiring the namespace normally:
(ns myapp.core
(:require [myapp.macros :as macros]))
(db-event …)
An alternative solution, instead of calling :require-macros in macros.cljs, is to :require the namespace with :include-macros true. As an example,
In macros.clj:
(ns myapp.macros)
(defmacro twice
[& body]
`(twice-helper (fn []
~#body)))
In macros.cljs:
(ns myapp.macros)
(defn twice-helper
[f]
(f)
(f))
Then we can use the macro in core.cljs:
(ns myapp.core
(:require [myapp.macros :as macros :include-macros true]))
(macros/twice (println "hello!"))
Related
Is it possible to insert custom forms into Clojure's ns declarations?
For example,
(ns
(:refer-my-library :exclude [something])
(:require [x :as y]))
Why would you do that? Depending on your answer, ns metadata might be a better alternative:
(ns foo {:my-data [:something]})
(meta (the-ns 'foo)) ;;=> {:my-data [:something]}
No. Well, you shouldn't. While the keywords like :require and :refer-clojure represent real functions or macros, they are assumed to be in the clojure.core namespace.
See the definition of ns, where it converts the directive :require into clojure.core/require.
(symbol "clojure.core" (clojure.core/name kname))
I am confused about the .cljc file format. I was wondering if inside a .cljc source file can Clojure and ClojureScript functions interact together. Also I was wondering if I can call from cljc to clj, cljs source file. For example if I define a function inside a .cljc source file can I call that function from the ClojureScript source file?
I was wondering if inside a .cljc source file can Clojure and
ClojureScript functions interact together.
Yes, that's possible and especially trivial if the common code is platform independent, for example:
(defn my-reduce [xs]
(reduce + xs))
All of the functions and forms in the above code exist in both ClojureScript and Clojure, so you don't need to do anything extra to make it work.
It is also possible to include platform dependent sections of code, by using reader conditionals:
(ns my-namespace.foo
(:require
[clojure.string :refer [split]]
#?(:clj [clojure.data.json :as json])))
(defn to-json [x]
#?(:cljs (clj->js x)
:clj (json/write-str x)))
In the above code, the standard reader conditional #? is used.
if I define a function inside a .cljc source file can I call that
function from the ClojureScript source file?
Yes, absolutely, but just be careful to ensure that the code that you call doesn't include any JVM-specific code.
In my code example, you could call to-json, from either ClojureScript or Clojure because I've been careful to isolate the platform differences inside reader conditionals.
I have a macro as such:
Example:
(defmacro xxz [& fns] `(:body ~#(map (fn [[e1 e2]] `(~e2 "http://www.google.com")) fns)))
If I pass something like (xxz [client/get client/get]), the resulting macroexpand shows that the symbols weren't qualified:
(:body (client/get "http://www.google.com"))
This causes problems when something like this lands in a namespace that doesn't have client imported.
Does anybody know what to do?
This sounds like a design oversight with syntax-quote (in my opinion), though it's not clear what a general "fix" would look like.
here is a more minimal example:
yummly.mobile-api.main> (in-ns 'foo)
#namespace[foo]
foo> (clojure.core/refer-clojure)
foo> (require '[org.httpkit.client :as client])
nil
foo> (defmacro xxz [& fns]
`(~#fns))
#'foo/xxz
foo> (macroexpand-1 '(xxz client/get))
(client/get)
from a new namespace:
foo> (in-ns 'bar)
#namespace[bar]
bar> (macroexpand-1 '(foo/xxz client/get))
(client/get)
bar> (foo/xxz client/get)
CompilerException java.lang.RuntimeException: No such namespace: client, compiling:(*cider-repl api*:87:6)
the syntax-quote-form (aka `) looks at every symbol in the expression as it's being compiled and if it does not find a / then it assumes it's for the local namespace and appends the current namespace. It can only do this for symbols that are present in the actual macro at the time the macro is defined, not (as I would like it to be able to do) for symbols that are passed as arguments to the macro.
If you use fully namespace qualified symbols in your macro then you don't need to worry if namepace where people use your macro has these symbols mapped to anything, because they spell out the full path to the symbol in it's name. If you don't want to actually type the full namespace in the symbols you use in your macro, then you can use :refer [get] in the require statement in your ns expression, this will save you having to type out the name and cause them to be correctly namespace expanded at the time that the macro definition is evaluated.
Because syntax quote is producing unhygenic symbols, you will need to either both :require or :refer to the functions in your ns section at the top of the namespace for every client where these namespaces are passed to the function.
Another option is to find some way to make sure the symbol is available to the macro at the time the syntax-quote is compiled. This can be tricky in some cases.
I have namespace with debug utilities that are used only in development. I'd like to make them accessible in all namespaces without qualifier (same as symbols from clojure.core).
Let's say my project structure is as follows:
dbg_utils.clj:
(ns project.dbg-utils)
(defmacro dbg ...)
db.clj
(ns project.db)
(defn create-entity [...]
...)
After I'd like to fire up REPL and type something like this:
> (require 'project.db)
> (require 'project.dbg-utils)
> (globalize-symbol 'project.dbg-utils/dbg)
And after use dbg macro without qualifier:
(ns project.db) ;; no require of project.dbg-utils
(defn create-entity [...]
(dbg ...) ;; and no qualifier here
...)
Is anything like globalize-symbol (or close to this) available?
Leiningen provides the :injections feature and the :user profile for that.
This article shares some pointers on how to do that. It basically works by adding the debugging functions you want to clojure.core and since all public vars from this namespace are always included when using the ns macro (unless you specify otherwise), you will have them available in all your namespaces.
I have a dictionary of words stored in a vector in one ns (ns dictionary.core) and I want to have access the vector in another namespace, for example, (ns clojure-project.core) How should I do it? I have been researching the concept of namespaces for some time and I am still confused as to how I can "import" variables defined in another file into my current project.
(ns clojure-project.core
(:require [dictionary.core :as dict]))
(defn choose-a-word []
(rand-nth (dict/words)))
Namespace declarations have many options, so it can be very confusing! Limiting yourself to the above form is in my opinion good style.