Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Two Ways to Access Properties in ClojureScript

DZone's Guide to

Two Ways to Access Properties in ClojureScript

· Web Dev Zone ·
Free Resource

Learn how error monitoring with Sentry closes the gap between the product team and your customers. With Sentry, you can focus on what you do best: building and scaling software that makes your users’ lives better.

There are two pairs of complementary functions to set properties on objects in ClojureScript. One is aset and aget, another is set! and .-propname:

(def scope (js-obj))
(aset scope "var1" "Value")
(aget scope "var1")
(def scope (js-obj))
(set! (.-var2 scope) "Value")
(.-var2 scope)

Are they equivalent? Is syntax the only difference?

For a demo, let’s say we set and print two fields on an object, like this:

(def scope (js-obj))
(aset scope "var1" "Value")
(set! (.-var2 scope) "Value")
 
(.log js/console (str "(aget scope \"var1\") ->" (aget scope "var1")))
(.log js/console (str "(.-var2 scope) ->" (.-var2 scope)))
(.log js/console (str "(.-var1 scope) ->" (.-var1 scope)))
(.log js/console (str "(aget scope \"var2\") ->" (aget scope "var2")))

The resulting “setting” instructions are deceptively similar:

mynamespace.scope = {};
mynamespace.scope["var1"] = "Value";
mynamespace.scope.var2 = "Value";

And of course all combinations print out correctly – I can see this on the console:

(aget scope "var1") ->Value
(.-var2 scope) ->Value
(.-var1 scope) ->Value
(aget scope "var2") ->Value

So far so good.

Now, if you’re at least a little bit serious about using ClojureScript, you have to consider advanced optimizations. What happens in this mode?

My code got compiled to:

var Xm;
Xm = {var1:"Value", lc:"Value"};

And on the console all I see is:

(aget scope "var1") ->Value
(.-var2 scope) ->Value
(.-var1 scope) ->
(aget scope "var2") ->

What happened to var2? Why is set! suddenly incompatible with aget and aset with .-field?

If you look at compiler output in both cases, you should see that in one case the value is bound by name and in the other by symbol. In my understanding, it has a few consequences:

  • Advanced compiler renames symbols, but leaves strings intact.
  • aset and aget operate on names. set! and .-var operate on symbols. If you set a variable using “symbolic reference”, the compiler will rename the symbol so you won’t be able to get it by name anymore. If you set it by name with aset, reading by symbol as .-var will not work either because the symbol is renamed.
  • If you deal only with ClojureScript, you should probably use the symbolic references. They look more idiomatic and will benefit from advanced compiler features such as minimization. On the other hand, in this case you often may want to leverage the idiomatic data bindings – vars, atoms and such.
  • As soon as you want your names to be accessible from outside, or you want to access names set outside, use onlyaset/aget. It obviously applies to interop with JavaScript libraries. Less obvious case (for me) was libraries that operate on DOM, like Angular or Knockout. If you want that {{user.name}} to work, you cannot use the symbolic version.

It all makes sense now, but I haven’t seen it documented. Well, that’s still true for most ClojureScript.



What’s the best way to boost the efficiency of your product team and ship with confidence? Check out this ebook to learn how Sentry's real-time error monitoring helps developers stay in their workflow to fix bugs before the user even knows there’s a problem.

Topics:

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}