Creating a variable named by a string in clojurescript

Hello Clojureverse!

I am trying to define a global variable named by a string, in clojurescript.

In clojure I would do the following:

(intern *ns* (symbol "my-var") "its-definition")
my-var => "its-definition" 

Is there an equivalent of the intern function in clojurescript? If not, how could I achieve the same result?

Thanks a lot!

intern not implemented - no reified Vars

I’m not sure that you can do it. I think the only option that might work is with a Macro, but even then I’m not so sure.

Would be easier to have an example of what you are actually trying to do? CLJS doesn’t have vars at runtime so creating one doesn’t really give you anything.

Why do you want to create a variable named by a string?

1 Like

I’m not sure that you can do it. I think the only option that might work is with a Macro, but even then I’m not so sure.

A macro should do the trick, but that would have to be in a .clj-file. Which returns us to the problem, I guess?

(defmacro defstr [sym-name & args]
  `(def ~(symbol sym-name) [email protected]))

(-> '(defstr "x" 99)
    macroexpand-1)
;; => (def x 99)

If CLJS doesn’t have vars at runtime, that makes me very curious about how self-hosted CLJS works.

1 Like

But even that, it only works if you have your string you want to create a Var from at compile time. If you wanted to say have the user type in a string at run-time and create a var from it. I’m not sure you can do that with ClojureScript macros.

Self hosted ClojureScript bundles the compiler with it, so you can’t reify Vars, but you can recompile the code itself and reload it.

At least that’s my understanding.

Vars in ClojureScript are just JS Vars, and I think namespaces are some kind of Goog.Closure nested JS object set.

The funny thing is, you can use set! it seems, and it’ll create a JS var that resolves, but it’ll keep warning that it is undeclared, since I’m guessing it doesn’t get added to some map of bindings.

> (set! foo 10)
WARNING: Use of undeclared Var cljs.user/foo at line 1 
> foo
10

That doesn’t help either since set! also takes in a Symbol and is a special form so you can’t evaluate its args.

2 Likes

I want to create reagent components that are being generated from a json file.

I do not know in advance the name of each component, they are defined in this json file.

So for instance for a given json file:

    {
      "HelloButton": "Click me!"
    }

I want to make the following reactify component:

    (def HelloButton
      (r/reactify-component
       (fn []
         [:button "Click me!"])))

I was trying this kind of macro as well, but without much success.
For instance using your example, this would not define foo when used like this:

(doseq [[n v] ["foo" :foo] ["bar" :bar]]
  (defstr n v))

But maybe this is to be expected from the way clojurescript works.

Thanks for the explanation, it gets clearer :grinning:

If you don’t know the name then how would you refer to the def afterwards?

Maybe you can just create the components at runtime and store them in a map? Don’t need a def if you don’t know the name anyways?

I know how to generate it from the json file.

The problem by doing this at runtime is that I won’t have a reagent component, or am I missing something?

You can define the component at runtime without a macro just fine. It all depends on how you are actually planning to use the component which you have not described yet.

I will try to describe a bit further what I want to achieve then.

I am building a React library of components generated from a json file. This file is parsed by clojurescript functions in order to create the react components, for that I use reagent. My project is based on shadow-cljs.

So the workflow is the following:

; source.json
{
      "HelloButton": "Click me!"
}

Which gets parsed by my cljs project and should generate a HelloButton react component into a dist.js file that can be imported in react.
So for example, if one wants to use this component, she could do the following:

/* app.js */
import {SimpleButton} from 'dist.js';
ReactDOM.render(
    <SimpleButton />,
    document.getElementById('root'));

The component name SimpleButton has been declared in the json file, and it is this same name that should be able to use in the javascript file.

I’m confused by the import {SimpleButton} from 'dist.js'; part since shadow-cljs does not support generating a file that would allow this?

Also why are you generating reagent components when they are getting used from JS?

In general when doing stuff like this it is almost always easier to just generate the source-code directly from the .json data. So don’t go with a macro, just generate .js or .cljs files separately in a simple script and just compile them regularly. Much easier to debug too.

Yes, this would be the ideal way. Currently, I am just doing a basic js import in the HTML and use the components name in the react file.

Why using reagent? well because I can leverage other tools like stylefy and I may be able to use the generated components directly in cljs at some point.

Maybe you are right; still, it seemed fairly easy to have my cljs parser working on a json file and staying in a workflow I feel comfortable and efficient. Plus it was just this one thing missing.

Thanks for your time anyway!

This is a limitation of non self-hosted ClojureScript. You can’t reify vars, and your macros cannot run at run-time.

This means only if you have access to the json file at compile time, when you convert the ClojureScript to JS, can you do it. But that’s not really allowing you to create components very dynamically, like say from a database at run-time or from user input at run-time.

At least I’m pretty sure that’s the case.

Well that’s my case actually! I have this information at compilation time. But I did not manage to find a way.

Oh you do!

Then a macro should be able to handle it. You need to load and parse the JSON inside the macro itself though. Something like:

(defmacro defcomponents [path-to-json]
  (let [json (slurp path-to-json)
        data (json/parse json)]
    `(do
      [email protected](for [row data]
        `(def ~(symbol (:name row)) [email protected](:body row)))))

Typed on my phone, but this should give you the gist.

This macro will run in Clojure, prior to compilation into JS, at that time, it will load the json, parse it, and generate a def for each components defined in it, and then the returned defs will get compiled by ClojureScript into JS.

1 Like

Ah that’s the trick to slurp the json inside the macro, I got to test that, but looks promising. I’ll let you know, many thanks!

1 Like