Listing Modules under a Namespace in Elixir
Tags: elixir, erlang, phoenix, programming
Published: 2022-02-21 15:58:40

In Elixir, it is possible to get a list of compiled modules under an application using Erlang's `application` module.

Let's first see how we can obtain a list of all compiled modules, then we'll discuss the why and what.

## How to get a list of compiled modules

I'll try to dig in and reference relevant tools by building upon this Stack Overflow [answer](https://stackoverflow.com/a/41738708) by Aleksei Matiushkin.
 
Reading from Erlang's [Reference Manual](https://www.erlang.org/doc/man/application.html), the application module _interacts with application controller, a process started at every Erlang runtime system. This module contains functions for controlling applications (for example, starting and stopping applications), and functions to access information about applications (for example, configuration parameters)_.

In our code, we can communicate with the `application` module via the lowercase atom `:application`, just like the rest of Erlang’s extensive standard library. All Erlang modules are represented by lowercase atoms such as `:os` and `:timer`.

`:application.get_key/2` will return a list of all compiled modules if given `:modules` atom as the second argument.

```elixir
{:ok, modules} = :application.get_key(:app_name, :modules)
[
 App,
 App.ModuleA, App.ModuleA.ModuleX,
 App.ModuleB, App.ModuleB.ModuleY
]
```

We can then:

- filter certain modules by binary pattern matching or by using `split/1` and `concat/1` functions under [Module](https://hexdocs.pm/elixir/1.13.3/Module.html)
- call their functions (using [Kernel](https://hexdocs.pm/elixir/1.13.3/Kernel.html)'s `apply/3`)
- do whatever that serves our purpose by using the capabilities of the [Stream](https://hexdocs.pm/elixir/1.13.3/Stream.html) and [Enum](https://hexdocs.pm/elixir/1.13.3/Enum.html) modules.

```elixir
{:ok, modules} = :application.get_key(:app_name, :modules)

modules
|> Stream.map(&Module.split/1)      # split
|> Stream.filter(fn module ->       # filter
  case module do
    ["App", "Namespace", "Base"] -> false
    ["App", "Namespace", _] -> true
     _ -> false
  end
end)
|> Stream.map(&Module.concat/1)     # concat
|> Stream.map(&{&1, apply(&1, :some_module_fn, [])})
|> Enum.map(fn output ->
  # ... do whatever
end)
```

## Why do this?
![why would you do that](https://media.giphy.com/media/X4YqmJEl6wJoY/giphy.gif)

Well, besides the fact that we can, I'll give a few examples from my own projects.

---

In [Rubik](https://github.com/mcaneris/rubik) (a work-in-progress visual scripting language implementation for a thesis project), I represented the visual computation nodes as Elixir modules conforming to a specific [Protocol](https://hexdocs.pm/elixir/1.13.3/Protocol.html), such as `Nodes.Arithmetic.Add`, `Nodes.Logic.And`, or `Nodes.List.Count`. 

Rubik is an open-source library, so developers integrating the library into their applications can code new nodes (specific to their use cases) that their users then can use in the visual scripting interface. 

The visual scripting interface provides a dropdown menu that a user can select a node from. This dropdown is essentially populated by a list of available modules under the `Nodes` namespace via a similar pipeline. 

So to add a new node, all a developer has to do is to code a module under the `Nodes` namespace and compile, and the node appears in the dropdown.

---

In Watchtower, I try to categorize smart contracts based on their interface via available ABIs (Application Binary Interface). Again, I represented smart contract types as Elixir modules living under a `Contracts` namespace. 

Each module has a module attribute `@schema` that contains a subset interface that I use to match an ABI to a contract type. 

```elixir
  @schema [
    {"name", [], [:address]},
    {"symbol", [], [:string]},
    {"token0", [], [:address]},
    {"token1", [], [:address]},
  ]
```

Each module also implements (better yet adopts via a [Behaviour](https://elixirschool.com/en/lessons/advanced/behaviours)) a `match/1` function that compares the given ABI to the subset under `@schema`. Basically, the pipeline becomes a meta `factory` that returns the correct module given an ABI.

Of course, it's always possible to implement a factory module that would do the same thing, but this way, a single module attribute gets the whole job done.