I did create my first open source project for Elixir: Umbra, the next ExActor
Sylvain Corsini
Posted on June 6, 2020
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 likeb
. - The variable
_nothing
is used but named with a_
, declare it likenothing
. - ...
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
Posted on June 6, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.