exat

Elixir Module and Struct Interoperability for Erlang


License
Apache-2.0

Documentation

exat: Elixir Module and Struct Interoperability for Erlang.

Write erlang friendly module names and get them translated into the right Elixir module names automatically.

The project is a parse transform but also an escript to easily debug if the transformation is being done correctly.

See the exat_example project for a simple usage example.

Erlang Friendly Elixir Module Names

A call like ex@A_B_C:my_fun(1) will be translated automatically to 'Elixir.A.B.C:my_fun(1)' at build time using a parse transform.

The trick is that the @ symbol is allowed in atoms if it's not the first character (thank node names for that). We use the ex@ prefix to identify the modules that we must translate since no one[1] uses that prefix for modules in erlang.

Aliases for Long Elixir Module Names

Since Elixir module names tend to nest and be long, you can define aliases to use in your code and save some typing, for example the following alias declaration:

-ex@alias(#{ex@Baz => ex@Foo_Bar_Baz,
            bare => ex@Foo_Long}).

Will translate ex@Bar:foo() to ex@Foo_Bar_Baz:foo() which in turn will become 'Elixir.Foo.Bar.Baz:foo()

It will also translate the module name bare:foo() into ex@Foo_Long:foo() which in turn will become Elixir.Foo.Long:foo()

Creating Structs

The code:

ex:s@Learn_User(MapVar)

Becomes:

'Elixir.Learn.User':'__struct__'(MapVar)

The code:

ex:s@Learn_User(#{name => "bob", age => 42})

Becodes:

'Elixir.Learn.User':'__struct__'(#{name => "bob", age => 42})

Which in Elixir would be:

%Learn.User{name: 'bob', age: 42}

Aliases in Structs

The following alias declaration:

-ex@alias(#{ex@struct_alias => ex@Learn_User}).

Will expand this:

    ex:s@struct_alias(#{name => "bob", age => 42})

Into this:

    'Elixir.Learn.User':'__struct__'(#{name => "bob", age => 42})

Pattern Matching Structs

Function calls are not allowed in pattern match positions, for example on function/case/etc clauses or the left side of a =, for that there's a different syntax:

get_name({ex@struct_alias, #{name := Name}}) ->
    Name;
get_name({ex@struct_alias, #{}}) ->
    {error, no_name}.

Becomes:

get_name(#{'__struct__' := 'Elixir.Learn.User',
           name := Name}) ->
    Name;
get_name(#{'__struct__' := 'Elixir.Learn.User'}) ->
    {error, no_name}.

And:

{ex@struct_alias, #{name := _}} = ex:s@Learn_User(#{name => "bob", age => 42})

Becomes:

#{'__struct__' := 'Elixir.Learn.User', name := _} =
	'Elixir.Learn.User':'__struct__'(#{name => "bob", age => 42})

This is because that pattern will match maps that also have other keys.

Note on Static Compilation of Literal Structs

On Elixir if you pass the fields to the struct it will be compiled to a map in place since the compiler knows all the fields and their defaults at compile time, for now exat uses the slower version that merges the defaults against the provided fields using 'Elixir.Enum':reduce in the future it will try to get the defaults at compile time if the struct being compiled already has a beam file (that is, it was compiled before the current file).

Use

Add it to your rebar.config as a dep and as a parse transform:

{erl_opts, [..., {parse_transform, exat}, ...]}.
{deps, [exat, ...]}

Note: Check if can be only a build profile dependency.

Build

To build the escript:

$ rebar3 escriptize

Run

You can run it as an escript:

$ _build/default/bin/exat pp [erl|ast] path/to/module.erl

For example in this repo:

$ _build/default/bin/exat pp erl resources/example1.erl
$ _build/default/bin/exat pp ast resources/example1.erl

License

Apache License 2.0 See LICENSE file for details

[1] Famous last words