Should we have namespaced atoms in Elixir?

My idea comes from qualified keywords from Clojure, at least after I got in contact with that in Clojure I felt Elixir was missing something like that. So just a small explanation of what is a qualified keyword in Clojure that I got from this blog post:

Brief: the five common forms

  1. :foo, which is just your plain old keyword
  2. ::foo, which is a namespaced keyword for the current namespace
  3. :my.ns/name, which is a namespaced keyword for a valid namespace
  4. ::my/name, which uses the :as alias to achieve the same as form #3
  5. :something/foo, which is commonly used with Datomic and doesn’t actually map to a valid namespace

I think 4 can be ignored for now but to understand 2 and 4:

(ns some.namespace)

(def foo {::bar 1}) 

outside of the some.namespace ::bar gonna be :some.namespace/bar.
You still can have 5, that is :something/foo that doesn’t actually relate to any existing namespace.

The idea is that we can have some context aware keyword/atom. I’m not suggesting binding atoms to module namespaces or something like that, but I guess we can initially do something like 5 at least for now.

Now going effectively to Elixir stuff.

We can have @ inside of an atom that doesn’t break the atom syntax, either in Elixir or Erlang. We could use that to standardize in the community an approach for namespaced atoms, something like :foo@something. The idea is that we can read that as “foo at something”.

Why use that instead of just old snake case? Exactly because that a simple snake case doesn’t communicate any intetion of namespacing or context related stuff to an atom.
Just as an example, let’s say want I to store api tokens for facebook and twitter I could use %{twitter_api_token: twitter_token, facebook_api_token: fb_token} I could have something like %{api_token@twitter: twitter_token, api_token@facebook: fb_token}. I think this even help avoid unneccesarry nesting of structures.

2 Likes

Elixir modules are namespaced atoms. They‘re :"Elixir.Module.Name“ aka Module.Name in short. Though the syntax sugar included in elixir doesn‘t really make that reusable.

3 Likes

I think you can alias arbitrary “module-style” atoms, they don’t have to actually be modules, which gets you some of the way there.

1 Like

I’m not sure what problem is this addressing. Is it about using bare maps? Can you give more examples?

You can express the intention by using a struct:

%ApiTokens{
  twitter: "...",
  facebook: "..."
}

Using bare maps builds technical debt pretty quickly, whereas a struct defines the exact shape of data.

1 Like

I don’t think use of bare maps is a problem, if their contents are still well defined/understood. E.g. optional keys cannot be modeled with structs, which can be limiting depending on the use case.

How about this?

%{
  {:api_token, :twitter} => twitter_token, 
  {:api_token, :facebook} => fb_token
}

Maybe a bit more verbose, but certainly less foreign within elixir.

Yep, I’ve thought of that too, the biggest issue I see with that is that there is no indication of an end of the namespace… so in the token example I’d had to do MyApp.Twitter.Token and MyApp.Facebook.Token there is no indication of where the namespacing ends where there is an identifier for the key.

I strongly disagree with that. As @LostKobrakai said, optional keys are not possible with structs, which usually makes you end up with a lot of different structs or bloated structs with a bunch of optional keys, both end up being way more harmful to codebases in the long term, at least in my experience.
Besides that, structs impose a strongly defined shape for the data, the idea of namespaced atoms is to give contextual meaning to the data without defining a strong shape to it. This allows you to use the data across several different use cases without having to deal with enforced shapes to the data.

I thought of that too… I think the @ example has some sort of the same meaning without disrupting the usage of the approach. Just in your example, you were forced to use the => rocket notation to define the map, by using the @ you can still have the shortened syntax for just atoms.

%{api_token@twitter: twitter_token, api_token@facebook: facebook_token}

The only issue I see is that it may break syntax highlight tools. Other point that it may lack is filtering data, with the tuple approach it’s way easier with the current tools.

The idea is to give contextual meaning to the keys. Have you ever dealt with rdf, datomic or even rss/atom feeds? the idea is that you avoid nesting structures building context on namespaces.
Just an example, if you look at the Elixir Outlaws rss feed you gonna see a bunch of nodes that start with the itunes namespace, those are nodes that don’t have any meaning to rss/atom feeds but are used by a platform to build information from that data, for example:

<itunes:type>episodic</itunes:type>
<itunes:subtitle>The hallway track of the Elixir community</itunes:subtitle>
<itunes:author>Elixir Outlaws</itunes:author>
<itunes:summary>Elixir Outlaws is an informal discussion about interesting things happening in Elixir. Our goal is to capture the spirit of a conference hallway discussion in a podcast. </itunes:summary>
<itunes:image href="https://assets.fireside.fm/file/fireside-images/podcasts/images/4/4b3f9fe7-b118-4f29-b6ee-7baf2571b03c/cover.jpg?v=4"/>
<itunes:explicit>no</itunes:explicit>
<itunes:keywords>elixir, erlang, BEAM</itunes:keywords>

This allows podcasts to integrate their information with iTunes directly from their feed without having to compromise their data related to the format(rss/atom) with the information that they want in the platform.

This is just an example of where namespaced keys can be useful, in Datomic for example this allows you to aggregate information whatever the way you want without losing context for that information. One of the problems of nesting structures for context is that once you go inside the nesting part it loses all context that you built.

Purerl uses @ in atoms for namespacing (probably not in the way you intend to, but I find it cool anyways)

At that point you might be much better off served by tagged tuples – potentially on several levels, not just one, e.g. {:api_token, :twitter, "abcdef} – than doing dances with your own naming convention of module-like atom names. Because they’re just that: conventions. You have make all your code abide to them, whereas with complex tuples you can utilize pattern matching and eliminate bugs by the mere virtue of missing function head definitions [for invalid state].

Added bonus is that there’s already a fantastic library for that: Domo.

5 Likes