loading...

I did create my first open source project for Elixir: Umbra, the next ExActor

scorsi profile image Sylvain Corsini ・3 min read

I was looking for projects I can create for the Elixir community

When looking on the first used Hex packages, I did saw ExActor in the firsts pages. This project is not maintained since 2017 and I was really surprised to see "10K download yesterday"... It is a really good project which solves a minor issue with GenServer : there is a lot of boilerplat and code duplications... This is quite frustrating. ExActor solves that issues by adding macros to dynamically generate code at compile time.

What the issues with ExActor, you may ask ?

It is not extensible, it generates a lot of warnings, it did not generate optimized code and it didn't support last GenServer features like continue since the package is outdated.

Here we don't have any problem:

defmodule MyGenServer do
  use Exactor.GenServer

  defcast set_state(new_state),
    do: new_state(new_state)
end

defmodule Generated_MyGenServer do
  use GenServer

  def set_state(pid, new_state),
    do: GenServer.cast(pid, {:new_state, new_state})
  def handle_cast({:new_state, new_state}, _state),
    do: {:noreply, new_state}
end

But what's with this:

defmodule MyGenServer do
  use Exactor.GenServer

  defcast do_complicated_thing([head | _nothing], %{name: "name", id: id}, 42 = _b, %MyStruct{this: this} = c),
    do: complicated_stuff(id, head, c) 
end

defmodule Generated_MyGenServer do
  use GenServer

  def do_complicated_thing(pid, [head | _nothing], %{name: "name", id: id}, 42 = _b, %MyStruct{this: this} = c),
    do: GenServer.cast(pid, {:do_complicated_thing, [head | _nothing], %{name: "name", id: id}, _b, c)

  def handle_cast({:do_complicated_thing, [head | _nothing], %{name: "name", id: id}, 42 = _b, %MyStruct{this: this} = c}, _state),
    do: complicated_stuff(id, head, c)
end

And here it's a compiler warning party in the client-side function. Kind of errors here:

  • The variable this is defined but not used, declare it like _this.
  • The variable _b is used but named with a _, declare it like b.
  • The variable _nothing is used but named with a _, declare it like nothing.
  • ...

But in fact the code the user wrote is absolutely right (for the server-side function at least) !

So Umbra did born

I so created my own package : Umbra. Which aims to resolve every issues ExActor does. It's still in development but I manage to have a working version which does resolves those ExActor issues. Unfortunately, my package is not complete at the actual time.

I read the whole ExActor code, I try to understand it... and from what I remember, the code is not really comprehensive not because it is not documented but because it goes all over the place.

I tried to create my package in a certain manner that it could be easily read and easily understand. The only complicated part of my package is when it navigate through the Elixir AST to alter the code to resolve shadow/unshadow warnings and to optimize it.

In the last example:

defmodule MyGenServer do
  use Umbra.GenServer

  # This is not the same function declaration as ExActor
  defcast {:do_complicated_thing, [head | _nothing], %{name: "name", id: id}, 42 = _b, %MyStruct{this: this} = c},
    do: complicated_stuff(id, head, c) 
end

defmodule Generated_MyGenServer do
  use GenServer

  def do_complicated_thing(pid, [_head | _nothing] = umbra_var_1, %{name: "name", id: _id} = umbra_var_2, 42 = b, %MyStruct{this: _this} = c),
    do: GenServer.cast(pid, {:do_complicated_thing, umbra_var_1, umbra_var_2, b, c)

  # The same handle_cast than ExActor
end

What's to notice:

  • Umbra created umbra_var_* to optimize calls,
  • Umbra did shadow unused variables,
  • Umbra did unshadow used variables.

Amazing.

For those who are asking how did I manage to do this, check the ArgumentsGenerator Module and the FunctionGenerator Module. I will create a post on how to understand and navigate through the Elixir AST code. This is pretty easy to do. And both module could be easily extracted to its own hex package.

Actually codes are poorly documented, sorry. I focused on features and tests before docs.

The next step of my projects is to add a lot of documentation for users and for contributors. Also guides on how to use it, how to create extensions, ...

Note: The project did have some known issues with guards, actually, variables used in when clause are potentially shadowed in the declaration which create a compile-time errors. This is why the 0.1.0 version of Umbra (on hex) did not use the last features like shadowing/unshadowing variables and variables optimizations.

Links

You can check out my package on hex or on GitHub directly.

Posted on by:

scorsi profile

Sylvain Corsini

@scorsi

I'm a single dev in a company working on a pretty large project.

Discussion

pic
Editor guide