Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsBlogPro
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges
  • Chrome Extension
  • Skill Manager

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Convert Python Erlang

ASecurity

Convert Python code to idiomatic Erlang. Use when migrating Python projects to Erlang, translating Python patterns to idiomatic Erlang, or refactoring Python codebases for fault-tolerance, distributed computing, and concurrency. Extends meta-convert-dev with Python-to-Erlang specific patterns.

8 stars
0 votes
0 copies
0 views
Added 2/8/2026
developmentpythonrustexpressflasktestingdebuggingrefactoringgitapidatabase

Works with

api

Security Analysis

A100/100

Scanned 2/10/2026

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

Installs into .claude/skills of the current project.

Are you the author of Convert Python Erlang?

Add the live security badge to your README — it updates automatically with every re-scan.

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

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

Download with Pro
Files
SKILL.md
---
name: convert-python-erlang
description: Convert Python code to idiomatic Erlang. Use when migrating Python projects to Erlang, translating Python patterns to idiomatic Erlang, or refactoring Python codebases for fault-tolerance, distributed computing, and concurrency. Extends meta-convert-dev with Python-to-Erlang specific patterns.
---

# Convert Python to Erlang

Convert Python code to idiomatic Erlang. This skill extends `meta-convert-dev` with Python-to-Erlang specific type mappings, idiom translations, and tooling for transforming dynamic, garbage-collected Python code into functional, concurrent, fault-tolerant Erlang.

## 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**: Python types → Erlang types (dynamic → dynamic with pattern matching)
- **Idiom translations**: Python patterns → idiomatic Erlang
- **Error handling**: Exceptions → let it crash + supervision
- **Async patterns**: asyncio → processes + message passing
- **Concurrency model**: Threading/asyncio → lightweight processes + OTP
- **Class hierarchy**: OOP → functional + behavior modules

## This Skill Does NOT Cover

- General conversion methodology - see `meta-convert-dev`
- Python language fundamentals - see `lang-python-dev`
- Erlang language fundamentals - see `lang-erlang-dev`
- Reverse conversion (Erlang → Python) - see `convert-erlang-python`

---

## Quick Reference

| Python | Erlang | Notes |
|--------|--------|-------|
| `int` | `integer()` | Arbitrary precision in both |
| `float` | `float()` | IEEE 754 double precision |
| `bool` | `true` / `false` atoms | Erlang booleans are atoms |
| `str` | `binary()` or `string()` | Binary for UTF-8, list of codepoints for string |
| `bytes` | `binary()` | Direct mapping |
| `list[T]` | `list(T)` | Singly-linked lists |
| `tuple` | `tuple()` | Immutable, fixed-size |
| `dict[K, V]` | `map()` or `dict` module | Maps for Erlang 17+, dict for older |
| `set[T]` | `sets` module | Use sets:new() |
| `None` | `undefined` atom | Or use tagged tuples like `{ok, Value}` / `error` |
| `Union[T, U]` | Tagged tuples | `{type1, Value}` / `{type2, Value}` |
| `Callable` | `fun()` | Anonymous or named functions |
| `async def` | `spawn()` + process | Async → concurrent process |
| `@dataclass` | `record` or `map` | Records for structured data |
| `Exception` | `throw`/`error`/`exit` | Let it crash philosophy |

## When Converting Code

1. **Analyze source thoroughly** before writing target
2. **Map types first** - create type equivalence table
3. **Embrace immutability** - all data is immutable in Erlang
4. **Preserve semantics** over syntax similarity
5. **Adopt Erlang idioms** - don't write "Python code in Erlang syntax"
6. **Think in processes** - replace threads/async with lightweight processes
7. **Let it crash** - replace defensive exception handling with supervision
8. **Pattern match** - use pattern matching instead of if/else chains
9. **Test equivalence** - same inputs → same outputs

---

## Type System Mapping

### Primitive Types

| Python | Erlang | Notes |
|--------|--------|-------|
| `int` | `integer()` | Both have arbitrary precision |
| `float` | `float()` | IEEE 754 double precision |
| `bool` | `true` / `false` | Atoms, not a separate type |
| `str` | `binary()` | UTF-8 binary: `<<"hello">>` |
| `str` | `string()` | List of codepoints: `"hello"` = `[104,101,108,108,111]` |
| `bytes` | `binary()` | Raw binary data |
| `bytearray` | `binary()` | Immutable in Erlang |
| `None` | `undefined` | Atom, or use option pattern |

**Critical Note on Strings**: Erlang has two string representations:
1. **Binaries** (`<<"text">>`): More memory-efficient, preferred for UTF-8 text
2. **Lists** (`"text"`): Lists of integers (codepoints), legacy representation

Use binaries for modern Erlang code.

### Collection Types

| Python | Erlang | Notes |
|--------|--------|-------|
| `list[T]` | `list(T)` | Singly-linked, immutable |
| `tuple` | `tuple()` | Fixed-size, immutable, pattern matchable |
| `dict[K, V]` | `map()` | Modern maps (Erlang 17+): `#{key => value}` |
| `dict[K, V]` | `dict` module | Legacy dict module |
| `set[T]` | `sets` module | `sets:new()`, `sets:add_element()` |
| `frozenset[T]` | `sets` module | All Erlang collections are immutable |
| `collections.deque` | `queue` module | `queue:new()`, FIFO operations |
| `collections.OrderedDict` | `map()` | Maps maintain insertion order (Erlang 18+) |
| `collections.defaultdict` | `maps:get(Key, Map, Default)` | Use default parameter |
| `collections.Counter` | `map()` | Map with integer values |

### Composite Types

| Python | Erlang | Notes |
|--------|--------|-------|
| `class` (data) | `record` | Compile-time record definition |
| `class` (data) | `map()` | Runtime structured data |
| `class` (behavior) | `behaviour` module | gen_server, gen_statem, etc. |
| `@dataclass` | `-record(name, {fields})` | Record with type specs |
| `typing.Protocol` | `behaviour` | Behavior contracts |
| `typing.TypedDict` | `map()` with type spec | `-type my_map() :: #{field := type()}.` |
| `typing.NamedTuple` | `record` or `tuple` | Record preferred for clarity |
| `enum.Enum` | `atoms` | Use atoms for enumerated values |
| `typing.Literal["a", "b"]` | `atoms` | `a`, `b` as atoms |
| `typing.Union[T, U]` | Tagged tuples | `{ok, Value}` / `{error, Reason}` |
| `typing.Optional[T]` | `Value | undefined` | Or `{ok, Value}` / `error` |
| `typing.Callable[[Args], Ret]` | `fun()` | `fun((Args) -> Ret)` |

### Type Annotations → Type Specs

| Python | Erlang | Notes |
|--------|--------|-------|
| `def f(x: int) -> int` | `-spec f(integer()) -> integer().` | Function type specification |
| `def f(x: T) -> T` | `-spec f(T) -> T when T :: any().` | Generic type variable |
| `x: Any` | `any()` | Top type |
| `x: list[int]` | `[integer()]` | List of integers |
| `x: Optional[int]` | `integer() \| undefined` | Union type |

---

## Idiom Translation

### Pattern 1: None Handling (Option Pattern)

**Python:**
```python
# Optional chaining with walrus operator
if user := get_user(user_id):
    name = user.name
else:
    name = "Anonymous"

# Or simpler
name = user.name if user else "Anonymous"
```

**Erlang:**
```erlang
% Tagged tuple pattern
case get_user(UserId) of
    {ok, User} ->
        Name = maps:get(name, User);
    error ->
        Name = <<"Anonymous">>
end.

% Or with pattern matching in function head
get_user_name(UserId) ->
    case get_user(UserId) of
        {ok, #{name := Name}} -> Name;
        error -> <<"Anonymous">>
    end.
```

**Why this translation:**
- Python uses None/truthy checks; Erlang uses tagged tuples `{ok, Value}` / `error`
- Erlang's pattern matching is more explicit and compile-time checked
- Tagged tuples are the idiomatic Erlang way to represent optional values

### Pattern 2: List Comprehensions

**Python:**
```python
# List comprehension
squared_evens = [x * x for x in numbers if x % 2 == 0]

# Generator expression
total = sum(x * x for x in numbers if x % 2 == 0)
```

**Erlang:**
```erlang
% List comprehension
SquaredEvens = [X * X || X <- Numbers, X rem 2 == 0].

% Fold for aggregation
Total = lists:foldl(
    fun(X, Acc) when X rem 2 == 0 -> Acc + X * X;
       (_, Acc) -> Acc
    end,
    0,
    Numbers
).

% Or filter + map + sum
Total = lists:sum(
    lists:map(
        fun(X) -> X * X end,
        lists:filter(fun(X) -> X rem 2 == 0 end, Numbers)
    )
).
```

**Why this translation:**
- Erlang list comprehensions are syntactically similar to Python's
- Use `||` instead of `for`, guards instead of `if`
- For aggregation, `lists:foldl/3` is the standard approach
- List operations in Erlang are functional transformations

### Pattern 3: Dictionary Operations

**Python:**
```python
# Get with default
value = config.get("timeout", 30)

# Setdefault pattern
cache.setdefault(key, expensive_compute())

# Dictionary comprehension
squared = {k: v * v for k, v in items.items()}
```

**Erlang:**
```erlang
% Get with default
Value = maps:get(timeout, Config, 30).

% Update only if not present (immutable, returns new map)
NewCache = case maps:is_key(Key, Cache) of
    true -> Cache;
    false -> maps:put(Key, expensive_compute(), Cache)
end.

% Map comprehension (Erlang 18+)
Squared = maps:from_list([{K, V * V} || {K, V} <- maps:to_list(Items)]).

% Or more idiomatically with maps:map/2
Squared = maps:map(fun(_K, V) -> V * V end, Items).
```

**Why this translation:**
- Erlang maps are immutable; operations return new maps
- `maps:get/3` takes default as third parameter
- `maps:map/2` transforms values while preserving keys
- No direct equivalent to setdefault because of immutability

### Pattern 4: String Formatting

**Python:**
```python
# f-strings (Python 3.6+)
message = f"User {user.name} has {count} items"

# format method
message = "User {} has {} items".format(user.name, count)

# % formatting (old style)
message = "User %s has %d items" % (user.name, count)
```

**Erlang:**
```erlang
% io_lib:format/2 (returns iolist, needs flattening for string)
Message = io_lib:format("User ~s has ~p items", [Name, Count]),
FlatMessage = lists:flatten(Message).

% For binaries (more common in modern Erlang)
Message = iolist_to_binary(io_lib:format("User ~s has ~p items", [Name, Count])).

% String concatenation with binaries
Message = <<<<"User ">>/binary, Name/binary, <<" has ">>/binary,
            (integer_to_binary(Count))/binary, <<" items">>/binary>>.
```

**Why this translation:**
- Erlang uses `io_lib:format/2` with format specifiers: `~s` (string), `~p` (any term), `~w` (term), `~.2f` (float)
- Returns an iolist, not a string - flatten or convert to binary
- Binary concatenation is efficient but verbose; use `io_lib:format/2` for readability

### Pattern 5: Duck Typing → Behaviors

**Python:**
```python
# Duck typing - if it has a .read() method, it's file-like
def process_data(file_like):
    data = file_like.read()
    return parse(data)

# Works with files, StringIO, BytesIO, etc.
```

**Erlang:**
```erlang
% Behavior-based - explicit contract
-module(file_processor).

% Define behavior
-callback read(State :: any()) -> {ok, binary()} | {error, term()}.

% Use behavior
process_data(Module, State) ->
    case Module:read(State) of
        {ok, Data} -> parse(Data);
        {error, Reason} -> {error, Reason}
    end.

% Or use process-based abstraction
process_data(Pid) ->
    Pid ! {self(), read},
    receive
        {Pid, {ok, Data}} -> parse(Data);
        {Pid, {error, Reason}} -> {error, Reason}
    end.
```

**Why this translation:**
- Erlang uses explicit behaviors (`-behaviour(gen_server)`) instead of duck typing
- Behavior modules define callbacks that implementing modules must provide
- Process-based abstraction is more common: send messages, receive responses
- Compile-time checking of behavior implementations

### Pattern 6: Context Managers → Process Lifecycle

**Python:**
```python
# with statement for resource management
with open("data.txt") as f:
    data = f.read()
# File automatically closed

# Custom context manager
with lock_held(mutex):
    # Critical section
    pass
# Lock automatically released
```

**Erlang:**
```erlang
% File I/O with automatic cleanup
process_file(Filename) ->
    {ok, File} = file:open(Filename, [read]),
    try
        {ok, Data} = file:read(File, 1024 * 1024),
        process_data(Data)
    after
        file:close(File)
    end.

% Process-based resource management
with_lock(LockPid, Fun) ->
    LockPid ! {self(), acquire},
    receive
        {LockPid, acquired} ->
            try
                Fun()
            after
                LockPid ! {self(), release}
            end
    end.
```

**Why this translation:**
- Erlang uses `try...after` for cleanup guarantees (similar to Python's finally)
- Process-based patterns are common: spawn a process to manage the resource
- Processes can be supervised; if they crash, supervisor handles cleanup
- No __enter__/__exit__ protocol; use explicit try...after blocks

### Pattern 7: Async/Await → Processes and Message Passing

**Python:**
```python
# Async function
async def fetch_user(user_id: int) -> dict:
    await asyncio.sleep(0.1)  # Simulate I/O
    return {"id": user_id, "name": f"User {user_id}"}

# Concurrent execution
async def main():
    users = await asyncio.gather(
        fetch_user(1),
        fetch_user(2),
        fetch_user(3)
    )
    print(users)
```

**Erlang:**
```erlang
% Spawned process
fetch_user(UserId) ->
    timer:sleep(100),  % Simulate I/O
    #{id => UserId, name => iolist_to_binary(io_lib:format("User ~p", [UserId]))}.

% Concurrent execution with processes
main() ->
    Self = self(),
    spawn(fun() -> Self ! {user, 1, fetch_user(1)} end),
    spawn(fun() -> Self ! {user, 2, fetch_user(2)} end),
    spawn(fun() -> Self ! {user, 3, fetch_user(3)} end),

    % Collect results
    Users = [receive {user, Id, User} -> User end || Id <- [1, 2, 3]],
    io:format("~p~n", [Users]).

% Or use a process pool pattern
main() ->
    Tasks = [1, 2, 3],
    Results = pmap(fun fetch_user/1, Tasks),
    io:format("~p~n", [Results]).

pmap(Fun, List) ->
    Parent = self(),
    Pids = [spawn(fun() -> Parent ! {self(), Fun(X)} end) || X <- List],
    [receive {Pid, Result} -> Result end || Pid <- Pids].
```

**Why this translation:**
- Python's async/await creates coroutines; Erlang uses lightweight processes
- Erlang processes are true concurrency primitives (can run on different cores)
- Message passing replaces async callbacks
- No event loop needed - BEAM VM handles scheduling
- Processes are isolated and fault-tolerant

### Pattern 8: Exception Handling → Let It Crash

**Python:**
```python
# Defensive exception handling
def process_data(data):
    try:
        validate(data)
        transformed = transform(data)
        save(transformed)
        return {"status": "success"}
    except ValueError as e:
        return {"status": "error", "message": str(e)}
    except Exception as e:
        logger.error(f"Unexpected error: {e}")
        return {"status": "error", "message": "Internal error"}
```

**Erlang:**
```erlang
% Let it crash - supervisor will restart
process_data(Data) ->
    validate(Data),      % Crash if invalid
    Transformed = transform(Data),  % Crash if transform fails
    save(Transformed),   % Crash if save fails
    {status, success}.

% Supervisor tree handles failures
-module(my_supervisor).
-behaviour(supervisor).

init([]) ->
    SupFlags = #{
        strategy => one_for_one,
        intensity => 5,
        period => 60
    },
    ChildSpecs = [
        #{
            id => worker,
            start => {worker_module, start_link, []},
            restart => permanent,
            shutdown => 5000,
            type => worker
        }
    ],
    {ok, {SupFlags, ChildSpecs}}.

% Only catch errors at supervision boundaries
handle_request(Data) ->
    try
        process_data(Data)
    catch
        error:Reason -> {error, Reason};
        exit:Reason -> {error, {exit, Reason}}
    end.
```

**Why this translation:**
- Python: defensive programming with try/except everywhere
- Erlang: let processes crash, supervisors restart them
- Errors are expected; design for failure, not defensive coding
- Try/catch only at supervision boundaries or API boundaries
- Supervision trees provide fault isolation and recovery

### Pattern 9: Class Inheritance → Behavior + gen_server

**Python:**
```python
# Class hierarchy
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

# Usage
dog = Dog("Buddy")
print(dog.speak())
```

**Erlang:**
```erlang
% Behavior module (defines interface)
-module(animal).
-callback speak(Name :: binary()) -> binary().

% Implementation module
-module(dog).
-behaviour(animal).
-export([speak/1]).

speak(Name) ->
    <<Name/binary, " says Woof!">>.

% Or use gen_server for stateful objects
-module(dog_server).
-behaviour(gen_server).
-export([start_link/1, speak/1]).
-export([init/1, handle_call/3, handle_cast/2]).

start_link(Name) ->
    gen_server:start_link(?MODULE, Name, []).

speak(Pid) ->
    gen_server:call(Pid, speak).

init(Name) ->
    {ok, #{name => Name}}.

handle_call(speak, _From, #{name := Name} = State) ->
    Reply = <<Name/binary, " says Woof!">>,
    {reply, Reply, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.
```

**Why this translation:**
- Erlang has no inheritance; use behaviors for contracts
- Behaviors define callbacks that modules must implement
- For stateful objects, use gen_server (OTP behavior)
- Composition over inheritance is enforced
- Process-based objects can be supervised

### Pattern 10: Decorators → Parse Transforms or Wrapper Functions

**Python:**
```python
# Function decorator
@cache
def expensive_func(x):
    return compute(x)

# Property decorator
class Circle:
    @property
    def area(self):
        return 3.14159 * self.radius ** 2
```

**Erlang:**
```erlang
% No direct decorator equivalent - use wrapper functions
expensive_func(X) ->
    case cache:get(X) of
        {ok, Result} -> Result;
        error ->
            Result = compute(X),
            cache:put(X, Result),
            Result
    end.

% Records don't have methods - use functions
-record(circle, {radius}).

area(#circle{radius = R}) ->
    3.14159 * R * R.

% Or use maps with computed access
circle_area(#{radius := R}) ->
    3.14159 * R * R.

% Parse transforms for compile-time metaprogramming (advanced)
% Similar to decorators but requires compiler hooks
```

**Why this translation:**
- Erlang has no decorators; use explicit wrapper functions
- Parse transforms can modify AST at compile time (advanced, rarely needed)
- Functions are first-class; pass them as arguments for HOF patterns
- Records and maps don't have methods; use module functions instead

---

## Error Handling

### Python Exceptions → Erlang Error Model

| Python | Erlang | When to Use |
|--------|--------|-------------|
| `raise Exception("msg")` | `error({reason, Msg})` | Internal errors, let it crash |
| `raise ValueError("msg")` | `error(badarg)` | Invalid arguments |
| `try...except` | `try...catch` | API boundaries only |
| `try...finally` | `try...after` | Resource cleanup |
| Exception chaining | Nested tuples | `{error, {reason, {cause, SubReason}}}` |

### Exception Translation

**Python:**
```python
try:
    result = risky_operation()
except ValueError as e:
    handle_value_error(e)
except KeyError as e:
    handle_key_error(e)
except Exception as e:
    handle_generic_error(e)
finally:
    cleanup()
```

**Erlang:**
```erlang
try
    Result = risky_operation(),
    process_result(Result)
catch
    error:badarg -> handle_badarg();
    error:{badkey, _} -> handle_badkey();
    error:Reason -> handle_generic_error(Reason);
    exit:Reason -> handle_exit(Reason)
after
    cleanup()
end.
```

---

## Concurrency Patterns

### Threading → Lightweight Processes

**Python:**
```python
import threading

def worker(name, delay):
    time.sleep(delay)
    print(f"Worker {name} done")

threads = [
    threading.Thread(target=worker, args=(f"T{i}", 1))
    for i in range(5)
]

for t in threads:
    t.start()

for t in threads:
    t.join()
```

**Erlang:**
```erlang
worker(Name, Delay) ->
    timer:sleep(Delay),
    io:format("Worker ~s done~n", [Name]).

main() ->
    Pids = [
        spawn(fun() -> worker(io_lib:format("T~p", [I]), 1000) end)
        || I <- lists:seq(1, 5)
    ],

    % Wait for all to complete (using monitors)
    [begin
        Ref = monitor(process, Pid),
        receive
            {'DOWN', Ref, process, Pid, _} -> ok
        end
     end || Pid <- Pids].
```

### Asyncio → gen_server

**Python:**
```python
import asyncio

class Counter:
    def __init__(self):
        self.count = 0

    async def increment(self):
        self.count += 1

    async def get_count(self):
        return self.count

async def main():
    counter = Counter()
    await counter.increment()
    await counter.increment()
    count = await counter.get_count()
    print(f"Count: {count}")
```

**Erlang:**
```erlang
-module(counter).
-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, 0}.

handle_call(get_count, _From, Count) ->
    {reply, Count, Count}.

handle_cast(increment, Count) ->
    {noreply, Count + 1}.

% Usage
main() ->
    {ok, _Pid} = counter:start_link(),
    counter:increment(),
    counter:increment(),
    Count = counter:get_count(),
    io:format("Count: ~p~n", [Count]).
```

---

## Common Pitfalls

1. **Treating Erlang as Object-Oriented**: Erlang is functional. Don't try to recreate class hierarchies. Use behaviors, modules, and processes.

2. **Excessive Try-Catch**: Don't wrap everything in try-catch. Let processes crash and use supervisors for fault recovery.

3. **String Confusion**: Erlang strings are lists of integers. Use binaries (`<<"text">>`) for UTF-8 text in modern code.

4. **Mutable State Mindset**: All data is immutable. Operations return new values. Use processes for mutable state via message passing.

5. **List Concatenation in Loops**: `List ++ Element` creates a new list each time (O(n)). Use cons `[Element | List]` and reverse, or use `lists:reverse/2`.

6. **Ignoring Process Leaks**: Spawned processes live until they exit. Always ensure processes terminate or are linked/monitored.

7. **Not Using Pattern Matching**: Erlang's strength is pattern matching. Use it in function heads, case expressions, and receive clauses.

8. **Forgetting Tail Recursion**: Recursive functions must be tail-recursive to avoid stack overflow. Use accumulator parameters.

9. **Maps vs Records**: Records are compile-time, maps are runtime. Records are faster and type-checked, but less flexible.

10. **Process Bottlenecks**: Single process handling all messages becomes a bottleneck. Design for parallelism with process pools or parallel message handling.

---

## Tooling

| Tool | Purpose | Notes |
|------|---------|-------|
| `rebar3` | Build tool | Standard build tool for Erlang projects |
| `dialyzer` | Static analyzer | Type checking via success typing |
| `eunit` | Unit testing | Built-in unit test framework |
| `common_test` | Integration testing | OTP testing framework |
| `PropEr` | Property-based testing | Similar to Python's Hypothesis |
| `meck` | Mocking library | Mock modules for testing |
| `observer` | GUI profiler | Visual process/memory inspector |
| `dbg` | Tracing | Built-in tracing for debugging |
| `py2erl` | Transpiler | Experimental Python to Erlang compiler |
| `ErlPort` | Python interop | Call Python from Erlang |

---

## Examples

### Example 1: Simple - HTTP Request Handler

**Before (Python):**
```python
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/user/<int:user_id>', methods=['GET'])
def get_user(user_id):
    user = database.find_user(user_id)
    if user:
        return jsonify(user)
    else:
        return jsonify({"error": "Not found"}), 404

if __name__ == '__main__':
    app.run()
```

**After (Erlang):**
```erlang
% Using Cowboy web server
-module(user_handler).
-export([init/2]).

init(Req0, State) ->
    UserId = cowboy_req:binding(user_id, Req0),
    case database:find_user(binary_to_integer(UserId)) of
        {ok, User} ->
            Req = cowboy_req:reply(200,
                #{<<"content-type">> => <<"application/json">>},
                jsx:encode(User),
                Req0),
            {ok, Req, State};
        error ->
            Req = cowboy_req:reply(404,
                #{<<"content-type">> => <<"application/json">>},
                jsx:encode(#{error => <<"Not found">>}),
                Req0),
            {ok, Req, State}
    end.
```

### Example 2: Medium - Concurrent Data Processing

**Before (Python):**
```python
import asyncio

async def process_item(item):
    # Simulate processing
    await asyncio.sleep(0.1)
    return item * 2

async def process_batch(items):
    tasks = [process_item(item) for item in items]
    results = await asyncio.gather(*tasks)
    return sum(results)

# Usage
items = list(range(1, 11))
result = asyncio.run(process_batch(items))
print(f"Total: {result}")
```

**After (Erlang):**
```erlang
-module(batch_processor).
-export([process_batch/1]).

process_item(Item) ->
    timer:sleep(100),  % Simulate processing
    Item * 2.

process_batch(Items) ->
    Parent = self(),
    Pids = [spawn(fun() ->
                Result = process_item(Item),
                Parent ! {self(), Result}
            end) || Item <- Items],

    % Collect results
    Results = [receive {Pid, Result} -> Result end || Pid <- Pids],
    lists:sum(Results).

% Usage
% 1> Items = lists:seq(1, 10).
% 2> batch_processor:process_batch(Items).
% 110
```

### Example 3: Complex - Stateful Worker Pool with Supervision

**Before (Python):**
```python
import asyncio
from concurrent.futures import ThreadPoolExecutor
import queue

class WorkerPool:
    def __init__(self, num_workers=5):
        self.queue = queue.Queue()
        self.workers = []
        self.executor = ThreadPoolExecutor(max_workers=num_workers)

    def start(self):
        for i in range(5):
            future = self.executor.submit(self.worker, i)
            self.workers.append(future)

    def worker(self, worker_id):
        while True:
            try:
                task = self.queue.get(timeout=1)
                result = self.process_task(task)
                print(f"Worker {worker_id}: {result}")
                self.queue.task_done()
            except queue.Empty:
                continue

    def process_task(self, task):
        # Simulate work
        return task * 2

    def submit(self, task):
        self.queue.put(task)

    def shutdown(self):
        self.executor.shutdown(wait=True)

# Usage
pool = WorkerPool(num_workers=5)
pool.start()
for i in range(20):
    pool.submit(i)
```

**After (Erlang):**
```erlang
%% Worker pool supervisor
-module(worker_pool_sup).
-behaviour(supervisor).
-export([start_link/1, init/1]).

start_link(PoolSize) ->
    supervisor:start_link({local, ?MODULE}, ?MODULE, PoolSize).

init(PoolSize) ->
    SupFlags = #{
        strategy => one_for_one,
        intensity => 5,
        period => 60
    },

    Workers = [
        #{
            id => {worker, N},
            start => {worker, start_link, [N]},
            restart => permanent,
            shutdown => 5000,
            type => worker
        } || N <- lists:seq(1, PoolSize)
    ],

    {ok, {SupFlags, Workers}}.

%% Worker gen_server
-module(worker).
-behaviour(gen_server).
-export([start_link/1, submit_task/2]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).

start_link(WorkerId) ->
    gen_server:start_link({local, list_to_atom("worker_" ++ integer_to_list(WorkerId))},
                          ?MODULE, WorkerId, []).

submit_task(WorkerPid, Task) ->
    gen_server:cast(WorkerPid, {task, Task}).

init(WorkerId) ->
    {ok, #{worker_id => WorkerId}}.

handle_cast({task, Task}, #{worker_id := WorkerId} = State) ->
    Result = process_task(Task),
    io:format("Worker ~p: ~p~n", [WorkerId, Result]),
    {noreply, State}.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_info(_Info, State) ->
    {noreply, State}.

process_task(Task) ->
    Task * 2.

%% Pool manager
-module(pool_manager).
-export([start/1, submit/1]).

start(PoolSize) ->
    worker_pool_sup:start_link(PoolSize).

submit(Task) ->
    % Simple round-robin distribution
    Workers = [list_to_atom("worker_" ++ integer_to_list(N)) || N <- lists:seq(1, 5)],
    Worker = lists:nth(rand:uniform(length(Workers)), Workers),
    worker:submit_task(Worker, Task).

%% Usage
%% 1> pool_manager:start(5).
%% 2> [pool_manager:submit(I) || I <- lists:seq(1, 20)].
```

**Key differences:**
- Python: ThreadPoolExecutor with shared queue
- Erlang: Supervised worker processes with message passing
- Erlang workers are fault-tolerant (supervisor restarts failed workers)
- No shared state; each worker is an isolated process
- Erlang's supervision tree provides automatic recovery

---

## Limitations

Due to gaps in the `lang-erlang-dev` skill (5/8 pillars), external research was required for:
- **Serialization idioms**: JSON encoding/decoding with `jsx`, term serialization
- **Build/dependency management**: rebar3 usage, dependency declaration, release building
- **Metaprogramming details**: Parse transforms (mentioned but limited coverage)

These areas are covered in this skill based on external documentation and community patterns.

---

## See Also

For more examples and patterns, see:
- `meta-convert-dev` - Foundational patterns with cross-language examples
- `convert-python-rust` - Python → Rust conversion for performance focus
- `lang-python-dev` - Python development patterns
- `lang-erlang-dev` - Erlang development patterns

Cross-cutting pattern skills:
- `patterns-concurrency-dev` - Process patterns, message passing, supervision across languages
- `patterns-testing-dev` - Unit testing, property-based testing, mocking strategies across languages

---

## References

- [Erlang/OTP Documentation](https://www.erlang.org/doc/)
- [Learn You Some Erlang](https://learnyousomeerlang.com/)
- [Python to Erlang Converter](https://www.codeconvert.ai/python-to-erlang-converter)
- [py2erl - Python to Erlang Compiler](https://github.com/sash-ko/py2erl)
- [ErlPort - Python/Erlang Integration](http://erlport.org/docs/python.html)
- [Erlang for Python Programmers](http://openbookproject.net/py4fun/erlang/erlang.html)

Attribution

aRustyDevaRustyDev
View sourceSee grades on GitHubMore from aRustyDev →
SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

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 (0)

No comments yet. Be the first to comment!

SSkills Directory ProSkills Directory

Get any skill into Claude in one click.

Download any skill as a ZIP for Claude.ai, Claude Desktop, or .claude/skills. $9/mo.

See Pro

Related Skills

Clean Code

Pragmatic coding standards - concise, direct, no over-engineering, no unnecessary comments

304955 votes

Browser Extension Developer

Use this skill when developing or maintaining browser extension code in the `browser/` directory, including Chrome/Firefox/Edge compatibility, content scripts, background scripts, or i18n updates.

285172 votes

Seo Optimizer

SEO optimization with keyword analysis, readability assessment, technical validation, content quality. Use for search rankings, blog posts, content audits, or encountering keyword density, readability scores, meta tags, schema markup errors.

2222 votes

Google Official Seo Guide

Official Google SEO guide covering search optimization, best practices, Search Console, crawling, indexing, and improving website search visibility based on official Google documentation

1862 votes

Tanstack Start

Build a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per ...

10341 votes
View all in development →