jmitchell
Writing a library for use in both Elixir and Erlang
What is the best practice for implementing a library for use in both Elixir and Erlang? My goals include:
- minimize code duplication
- feels idiomatic to both Elixir and Erlang users
- no syntactic function call remapping like
:lists.sort [3, 2, 1]or'Elixir.String':downcase(Bin). - integrates seamlessly with common package management and build solutions
- doesn’t break any features like maybe hot code reloading (or anything else, really)
- written primarily in Elixir because I like
defmacro.
Without knowing better, the first strategy I’d probably try is implementing an Elixir library and making an Erlang wrapper. This seems good, except I’d have to manually keep the wrapper synced with the Elixir code. Are there recommended wrapper generators for this?
I’m not familiar enough with Elixir/Erlang/BEAM to know whether that approach might break any features. If so, would writing the library primarily in Erlang be any better?
I don’t have a specific project in mind yet. Just mulling over how I would approach this when the time comes. Thanks, everyone.
Marked As Solved
PragTob
So, @josevalim came around and suggested another way to do this which I like better as it is simpler:
elixirdoc = """
...
"""
erlangdoc = """
...
"""
for {module, moduledoc} <- [{Benchee, elixir_doc}, {:benchee, erlang_doc}] do
defmodule module do
@moduledoc moduledoc
# all defs here without using
end
end
It is implemented in this benchee PR.
Also Liked
michalmuskala
What I saw frequently in Erlang codebases using Elixir is the use of macros to alias elixir modules into something more consumable. You could provide an erlang header file that would provide some of those macros, eg:
-define(string, 'Elixir.String')
# later in code
?string:downcase(Bin).
On the other hand, I don’t see anything wrong with calling erlang-style modules from Elixir. You could use those:
defmodule :foo do
# ...
end
# use in elixir
:foo.bar(1, 2, 3)
# use in erlang
foo:bar(1, 2, 3).
In general I would say that a wrapper is the worst approach - it doesn’t add any significant value and has considerable downsides and risks regarding it getting out of sync.
vic
So if @jmitchell would like to be able to call Foo.bar nicely from both Elixir and foo.bar from Erlang, would this be an acceptable approach to expose the api for both ?
defmodule Foo.Impl do
defmacro __using__(_) do
quote do
def bar(), do: # ...
# more of Foo public API
end
end
end
defmodule :foo do
@moduledoc "For use from erlang"
use Foo.Impl
end
defmodule Foo do
@moduledoc "Alchemist way"
use Foo.Impl
end
PragTob
Not sure about the compiled artifact size but as it’s just one top level module that does a bunch of delegates to other things I don’t think the size impact (even if it duplicates all that code) is worth worrying about as I’d say it’s maybe ~1% of the overall code size.
I like the solution because it’s very simple and uses the simplest constructs to make it work - i.e. no macros.
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance








