Skip to content
Back to skills

Convert Erlang Elixir

ASecurity

Convert Erlang code to idiomatic Elixir. Use when migrating Erlang/OTP applications to Elixir, translating gen_server behaviors to GenServer, or refactoring BEAM VM code to leverage Elixir's modern syntax and tooling. Extends meta-convert-dev with Erlang-to-Elixir specific patterns.

  • 8 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added February 8, 2026
developmentgotestingrefactoringapidocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add aRustyDev/ai --skill convert-erlang-elixir --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Convert Erlang Elixir?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Convert Erlang Elixir
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/arustydev-convert-erlang-elixir/badge)](https://www.skillsdirectory.com/skills/arustydev-convert-erlang-elixir)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: convert-erlang-elixir
description: Convert Erlang code to idiomatic Elixir. Use when migrating Erlang/OTP applications to Elixir, translating gen_server behaviors to GenServer, or refactoring BEAM VM code to leverage Elixir's modern syntax and tooling. Extends meta-convert-dev with Erlang-to-Elixir specific patterns.
---

# Convert Erlang to Elixir

Convert Erlang code to idiomatic Elixir. This skill extends `meta-convert-dev` with Erlang-to-Elixir specific type mappings, idiom translations, and tooling for translating between these two languages that share the BEAM VM runtime.

## This Skill Extends

- `meta-convert-dev` - Foundational conversion patterns (APTV workflow, testing strategies)

For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.

## This Skill Adds

- **Type mappings**: Erlang types → Elixir types with modern syntax
- **Idiom translations**: Erlang patterns → idiomatic Elixir
- **OTP behaviors**: gen_server → GenServer, supervisor → Supervisor
- **Syntax modernization**: Records → Structs, -spec → @spec
- **Module system**: Erlang modules → Elixir modules with metaprogramming
- **Build tools**: rebar3 → Mix project structure

## This Skill Does NOT Cover

- General conversion methodology - see `meta-convert-dev`
- Erlang language fundamentals - see `lang-erlang-dev`
- Elixir language fundamentals - see `lang-elixir-dev`
- Reverse conversion (Elixir → Erlang) - see `convert-elixir-erlang`
- Phoenix framework specifics - see `lang-elixir-phoenix-dev`

---

## Quick Reference

| Erlang | Elixir | Notes |
|--------|--------|-------|
| `atom` | `:atom` | Atoms prefixed with `:` |
| `{ok, Value}` | `{:ok, value}` | Atoms and snake_case |
| `<<"binary">>` | `"binary"` | Strings are binaries |
| `[H\|T]` | `[h \| t]` | Same list syntax |
| `#{}` (map) | `%{}` | Map syntax similar |
| `#record{}` | `%Struct{}` | Records → Structs |
| `-module(name).` | `defmodule Name do` | Module definition |
| `-export([f/1]).` | `def f(arg)` | Public functions |
| `fun(X) -> X end` | `fn x -> x end` | Anonymous functions |
| `receive ... end` | `receive do ... end` | Message receiving |
| `gen_server` | `GenServer` | OTP behavior |
| `supervisor` | `Supervisor` | OTP supervisor |
| `?MODULE` | `__MODULE__` | Module reference |

---

## Type System Mapping

### Primitive Types

| Erlang | Elixir | Notes |
|--------|--------|-------|
| `atom` | `:atom` | Atoms require `:` prefix in Elixir |
| `integer` | `integer` | Arbitrary precision in both |
| `float` | `float` | 64-bit double precision |
| `binary` / `<<"string">>` | `"string"` | Elixir strings are UTF-8 binaries |
| `list` / `'charlist'` | `'charlist'` | Lists of integers |
| `true` / `false` | `true` / `false` | Boolean atoms |
| `undefined` | `nil` | Elixir uses `nil` atom |
| `pid` | `pid` | Process identifiers identical |
| `reference` | `reference` | Same on BEAM |

### Collection Types

| Erlang | Elixir | Notes |
|--------|--------|-------|
| `[H\|T]` | `[h \| t]` | Linked lists, same structure |
| `[]` | `[]` | Empty list |
| `{a, b, c}` | `{:a, :b, :c}` | Tuples identical, atoms need `:` |
| `#{}` | `%{}` | Maps (Erlang 17+) |
| `#{key => value}` | `%{key: value}` | Atom keys shorthand in Elixir |
| `orddict` | `Map` or `Keyword` | Use Elixir's Map module |
| `sets` | `MapSet` | Elixir's set implementation |

### Composite Types

| Erlang | Elixir | Notes |
|--------|--------|-------|
| `-record(user, {name, age}).` | `defstruct [:name, :age]` | Records → Structs |
| `#user{name=N, age=A}` | `%User{name: n, age: a}` | Record instance → Struct |
| `-type name() :: type().` | `@type name :: type` | Type specifications |
| `-spec func(type) -> type.` | `@spec func(type) :: type` | Function specs |
| `-callback func(type) -> type.` | `@callback func(type) :: type` | Behavior callbacks |

---

## Idiom Translation

### Pattern: Module Definition

**Erlang:**
```erlang
-module(my_module).
-export([public_function/1]).

-type my_type() :: atom() | binary().

public_function(Arg) ->
    private_function(Arg).

private_function(Arg) ->
    {ok, Arg}.
```

**Elixir:**
```elixir
defmodule MyModule do
  @moduledoc """
  Module documentation goes here.
  """

  @type my_type :: atom() | binary()

  @spec public_function(term()) :: {:ok, term()}
  def public_function(arg) do
    private_function(arg)
  end

  defp private_function(arg) do
    {:ok, arg}
  end
end
```

**Why this translation:**
- Elixir uses `defmodule` with do/end blocks
- Functions are public by default unless `defp` (private)
- `@moduledoc` and `@doc` for documentation
- CamelCase for module names, snake_case for functions

---

### Pattern: Function Clauses and Pattern Matching

**Erlang:**
```erlang
factorial(0) -> 1;
factorial(N) when N > 0 -> N * factorial(N - 1).

process_result({ok, Data}) ->
    {success, Data};
process_result({error, Reason}) ->
    {failure, Reason}.
```

**Elixir:**
```elixir
def factorial(0), do: 1
def factorial(n) when n > 0, do: n * factorial(n - 1)

def process_result({:ok, data}), do: {:success, data}
def process_result({:error, reason}), do: {:failure, reason}
```

**Why this translation:**
- Multiple function clauses work the same way
- Elixir uses `:` prefix for atoms
- `do:` for single-line functions, `do/end` for multi-line
- Guards work identically with `when`

---

### Pattern: Records to Structs

**Erlang:**
```erlang
-record(user, {
    id :: integer(),
    name :: binary(),
    email :: binary()
}).

create_user(Id, Name, Email) ->
    #user{id=Id, name=Name, email=Email}.

get_user_name(#user{name=Name}) ->
    Name.
```

**Elixir:**
```elixir
defmodule User do
  @enforce_keys [:id, :name, :email]
  defstruct [:id, :name, :email]

  @type t :: %__MODULE__{
    id: integer(),
    name: String.t(),
    email: String.t()
  }

  def new(id, name, email) do
    %User{id: id, name: name, email: email}
  end

  def get_name(%User{name: name}), do: name
end
```

---

### Pattern: gen_server to GenServer

**Erlang:**
```erlang
-module(counter_server).
-behaviour(gen_server).

-export([start_link/0, increment/0, get_count/0]).
-export([init/1, handle_call/3, handle_cast/2]).

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

increment() ->
    gen_server:cast(?MODULE, increment).

get_count() ->
    gen_server:call(?MODULE, get_count).

init([]) ->
    {ok, #{count => 0}}.

handle_call(get_count, _From, State = #{count := Count}) ->
    {reply, Count, State}.

handle_cast(increment, State = #{count := Count}) ->
    {noreply, State#{count := Count+1}}.
```

**Elixir:**
```elixir
defmodule CounterServer do
  use GenServer

  # Client API

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, :ok, opts ++ [name: __MODULE__])
  end

  def increment do
    GenServer.cast(__MODULE__, :increment)
  end

  def get_count do
    GenServer.call(__MODULE__, :get_count)
  end

  # Server Callbacks

  @impl true
  def init(:ok) do
    {:ok, %{count: 0}}
  end

  @impl true
  def handle_call(:get_count, _from, state) do
    {:reply, state.count, state}
  end

  @impl true
  def handle_cast(:increment, state) do
    {:noreply, %{state | count: state.count + 1}}
  end
end
```

**Why this translation:**
- `use GenServer` imports behavior
- `@impl true` marks callback implementations
- `__MODULE__` replaces `?MODULE`
- Maps update with `%{map | key: value}` syntax

---

### Pattern: Supervisor Trees

**Erlang:**
```erlang
-module(my_supervisor).
-behaviour(supervisor).

init([]) ->
    Children = [
        {counter_server,
         {counter_server, start_link, []},
         permanent, 5000, worker, [counter_server]}
    ],
    {ok, {{one_for_one, 5, 10}, Children}}.
```

**Elixir:**
```elixir
defmodule MySupervisor do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      {CounterServer, []}
    ]

    Supervisor.init(children, strategy: :one_for_one)
  end
end
```

---

### Pattern: Message Passing

**Erlang:**
```erlang
Pid = spawn(fun() -> loop(0) end),
Pid ! {self(), increment},
receive
    {reply, Msg} -> io:format("Got: ~p~n", [Msg])
after 5000 ->
    io:format("Timeout~n")
end.
```

**Elixir:**
```elixir
pid = spawn(fn -> loop(0) end)
send(pid, {self(), :increment})

receive do
  {:reply, msg} -> IO.puts("Got: #{inspect(msg)}")
after
  5000 -> IO.puts("Timeout")
end
```

---

### Pattern: Error Handling

**Erlang:**
```erlang
safe_divide(_, 0) -> {error, division_by_zero};
safe_divide(A, B) -> {ok, A / B}.

handle_file(File) ->
    try
        {ok, Content} = file:read_file(File),
        process(Content)
    catch
        error:Reason -> {error, Reason}
    end.
```

**Elixir:**
```elixir
def safe_divide(_a, 0), do: {:error, :division_by_zero}
def safe_divide(a, b), do: {:ok, a / b}

def handle_file(file) do
  try do
    {:ok, content} = File.read!(file)
    process(content)
  rescue
    e in File.Error -> {:error, e.reason}
  end
end

# With construct for error handling
def handle_file_with(file) do
  with {:ok, content} <- File.read(file),
       {:ok, processed} <- process(content) do
    {:ok, processed}
  end
end
```

---

## OTP Behavior Translation

| Erlang callback | Elixir callback | Notes |
|-----------------|-----------------|-------|
| `init/1` | `init/1` | Same signature |
| `handle_call/3` | `handle_call/3` | Synchronous requests |
| `handle_cast/2` | `handle_cast/2` | Asynchronous messages |
| `handle_info/2` | `handle_info/2` | Non-OTP messages |
| `terminate/2` | `terminate/2` | Cleanup |

---

## Syntax Modernization

### Pipe Operator

**Erlang:**
```erlang
Result = process(transform(filter(validate(Input)))).
```

**Elixir:**
```elixir
result =
  input
  |> validate()
  |> filter()
  |> transform()
  |> process()
```

### List Comprehensions

**Erlang:**
```erlang
[X*2 || X <- lists:seq(1, 10), X rem 2 =:= 0].
```

**Elixir:**
```elixir
for x <- 1..10, rem(x, 2) == 0, do: x * 2
```

---

## Common Pitfalls

### 1. Atom Syntax Confusion

```elixir
# WRONG
case result do
  ok -> :success
end

# CORRECT
case result do
  :ok -> :success
end
```

### 2. Variable Rebinding

```elixir
# Elixir allows rebinding
x = 1
x = 2  # OK

# Use pin operator to match
^x = 2  # Match error if x != 2
```

### 3. String vs Charlist

```elixir
string = "hello"   # Binary (UTF-8)
charlist = 'hello' # Charlist

# Convert between them
String.to_charlist("hello")
List.to_string('hello')
```

---

## Tooling

| Erlang | Elixir | Purpose |
|--------|--------|---------|
| `rebar3` | `Mix` | Build tool |
| `EUnit` | `ExUnit` | Testing |
| `Dialyzer` | `Dialyzer` | Type checker |
| `erlc` | `elixirc` | Compiler |
| `erl` | `IEx` | REPL |

### Migration Workflow

1. **Setup Elixir project**: `mix new my_app`
2. **Add Erlang dependencies** in `mix.exs`
3. **Call Erlang from Elixir**: `:erlang_module.function(args)`
4. **Gradual migration**: Keep Erlang in `src/` (auto-compiled by Mix)

---

## See Also

- `meta-convert-dev` - Foundational patterns
- `convert-elixir-erlang` - Reverse conversion
- `lang-erlang-dev` - Erlang patterns
- `lang-elixir-dev` - Elixir patterns

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.