xiaohongshu
xiaohongshu
convert-roc-erlang
Convert Roc code to idiomatic Erlang. Use when migrating Roc projects to Erlang/OTP, translating functional patterns to process-based architectures, or refactoring Roc codebases. Extends meta-convert-dev with Roc-to-Erlang specific patterns.
Full skill instructions
Convert Roc code to idiomatic Erlang. This skill extends meta-convert-dev with Roc-to-Erlang specific type mappings, idiom translations, and architectural patterns for moving from pure functional programming to process-based concurrency.
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.
meta-convert-devlang-roc-devlang-erlang-devconvert-erlang-roc| Roc | Erlang | Notes |
|---|---|---|
[Tag] | atom() | Tags become atoms |
I64 / U64 | integer() | Erlang integers are arbitrary precision |
F64 | float() | 64-bit float |
List U8 | binary() | Byte sequences |
List a | list() | Heterogeneous possible |
(a, b, c) | {a, b, c} | Fixed-size tuples |
Dict k v | map() or #{k => v} | Key-value maps |
Ok(value) | {ok, Value} | Success result |
Err(reason) | {error, Reason} | Error result |
Task a err | Process/gen_server | Platform tasks become processes |
{} -> T | fun(() -> T) | Zero-arg function |
None in tag union | undefined | Optional values |
| Roc Concept | Erlang Approach | Key Insight |
|---|---|---|
| Pure function with data | Function or process state | Processes add lifecycle management |
| Function composition | Message passing or function calls | Choose based on concurrency needs |
| Platform task | Process with message loop | Effects require process model |
| Task chaining | Sequential message sends | Or gen_server calls |
| Result type | Tagged tuples {ok, Val} / {error, Reason} | Explicit error tuples |
| Tag unions | Tagged tuples and pattern matching | Multiple atoms as tags |
| Platform capabilities | OTP behaviors (gen_server, supervisor) | Framework provides structure |
| Roc Model | Erlang Model | Conceptual Translation |
|---|---|---|
| Platform Tasks | Lightweight processes | Tasks become spawned processes |
| Task composition | Message passing | Chain via messages or calls |
| Pure data flow | Explicit message protocols | Messages are data flow |
| Platform error handling | Supervision trees | Let it crash philosophy |
| Platform-managed lifecycle | Process lifecycle + monitors | Explicit lifecycle management |
| Roc | Erlang | Notes |
|---|---|---|
Bool | true / false | Boolean atoms |
I8, I16, I32, I64, I128 | integer() | Arbitrary precision in Erlang |
U8, U16, U32, U64, U128 | integer() | Erlang doesn't distinguish signed/unsigned |
F32, F64 | float() | 64-bit floating point |
Str | string() / binary() | Use binary for efficiency |
List U8 | binary() | Byte sequences |
| Roc | Erlang | Notes |
|---|---|---|
List a | [a] | Erlang lists can be heterogeneous |
Dict k v | #{k => v} | Maps (Erlang 17+) |
Set a | sets:set(a) or gb_sets:set(a) | Standard library modules |
(A, B, C) | {A, B, C} | Direct tuple mapping |
| Roc | Erlang | Notes |
|---|---|---|
{ field : Type } | -record(name, {field :: type()}) | Records with type specs |
{ field : Type } | #{field => Type} | Or maps for flexibility |
[Tag] | atom() | Single tag becomes atom |
[Tag1, Tag2] | Tagged tuple or atom | Multiple tags use tuples |
[Tag(A)] | {tag, A} | Tag with payload |
Result a e | {ok, A} | {error, E} | Standard error convention |
| Roc | Erlang | Notes |
|---|---|---|
{} -> R | fun(() -> R) | Zero-argument function |
A -> R | fun((A) -> R) | Single argument |
A, B -> R | fun((A, B) -> R) | Multiple arguments |
Generic <a> | Dynamic typing | No generics needed |
| Roc | Erlang | Notes |
|---|---|---|
Ok(value) | {ok, Value} | Success case |
Err(reason) | {error, Reason} | Error case |
Result V R | {ok, V} | {error, R} | Result pattern |
| Tag unions for errors | Multiple error atoms/tuples | Explicit error variants |
Roc:
interface MathUtils
exposes [add, square]
imports []
add : I64, I64 -> I64
add = \a, b -> a + b
square : I64 -> I64
square = \n -> n * n
Erlang:
-module(math_utils).
-export([add/2, square/1]).
-spec add(integer(), integer()) -> integer().
add(A, B) -> A + B.
-spec square(integer()) -> integer().
square(N) -> N * N.
Why this translation:
exposes maps to -export-spec declarationsRoc:
processResult : [Ok Data, Err Reason, Unknown] -> [Success Data, Failure Reason]
processResult = \result ->
when result is
Ok(data) -> Success(data)
Err(reason) -> Failure(reason)
Unknown -> Failure(UnknownResult)
Erlang:
-spec process_result({ok, Data} | {error, Reason} | unknown) ->
{success, Data} | {failure, Reason}.
process_result({ok, Data}) ->
{success, Data};
process_result({error, Reason}) ->
{failure, Reason};
process_result(unknown) ->
{failure, unknown_result}.
Why this translation:
when becomes multiple function clausesRoc:
sum : List I64 -> I64
sum = \list ->
List.walk(list, 0, Num.add)
map : List a, (a -> b) -> List b
map = \list, fn ->
List.map(list, fn)
filter : List a, (a -> Bool) -> List a
filter = \list, pred ->
List.keepIf(list, pred)
Erlang:
-spec sum([integer()]) -> integer().
sum(List) ->
lists:foldl(fun(X, Acc) -> X + Acc end, 0, List).
-spec map([A], fun((A) -> B)) -> [B].
map(List, Fn) ->
lists:map(Fn, List).
-spec filter([A], fun((A) -> boolean())) -> [A].
filter(List, Pred) ->
lists:filter(Pred, List).
Why this translation:
List.walk becomes lists:foldlList.map becomes lists:mapList.keepIf becomes lists:filterRoc:
User : {
name : Str,
age : U32,
email : Str,
}
createUser : Str, U32, Str -> User
createUser = \name, age, email ->
{ name, age, email }
updateAge : User, U32 -> User
updateAge = \user, newAge ->
{ user & age: newAge }
getName : User -> Str
getName = \{ name } ->
name
Erlang:
-record(user, {
name :: string(),
age :: non_neg_integer(),
email :: string()
}).
-spec create_user(string(), non_neg_integer(), string()) -> #user{}.
create_user(Name, Age, Email) ->
#user{name=Name, age=Age, email=Email}.
-spec update_age(#user{}, non_neg_integer()) -> #user{}.
update_age(User, NewAge) ->
User#user{age=NewAge}.
-spec get_name(#user{}) -> string().
get_name(#user{name=Name}) ->
Name.
Why this translation:
Roc:
# Pure state machine - no processes
State : I64
init : State
init = 0
increment : State -> State
increment = \count ->
count + 1
getCount : State -> I64
getCount = \count ->
count
# Platform would provide state management primitives
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, handle_info/2,
terminate/2, code_change/3]).
%% API
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
increment() ->
gen_server:cast(?MODULE, increment).
get_count() ->
gen_server:call(?MODULE, get_count).
%% gen_server callbacks
init([]) ->
{ok, 0}.
handle_call(get_count, _From, Count) ->
{reply, Count, Count};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(increment, Count) ->
{noreply, Count + 1};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Why this translation:
Roc:
divide : F64, F64 -> Result F64 [DivisionByZero]
divide = \a, b ->
if b == 0 then
Err(DivisionByZero)
else
Ok(a / b)
safeDivide : F64, F64 -> F64
safeDivide = \a, b ->
divide(a, b)
|> Result.withDefault(0)
Erlang:
-spec divide(float(), float()) -> {ok, float()} | {error, division_by_zero}.
divide(_A, 0.0) ->
{error, division_by_zero};
divide(A, B) ->
{ok, A / B}.
-spec safe_divide(float(), float()) -> float().
safe_divide(A, B) ->
case divide(A, B) of
{ok, Result} -> Result;
{error, _} -> 0.0
end.
Why this translation:
{ok, Value} / {error, Reason}Result.withDefault becomes case with fallbackRoc:
findUser : U64, List User -> [Some User, None]
findUser = \id, users ->
users
|> List.findFirst(\user -> user.id == id)
|> Result.map(Some)
|> Result.withDefault(None)
getEmail : [Some User, None] -> Str
getEmail = \maybeUser ->
when maybeUser is
Some({ email }) -> email
None -> "no email"
Erlang:
-spec find_user(non_neg_integer(), [user()]) -> user() | undefined.
find_user(Id, Users) ->
case lists:keyfind(Id, #user.id, Users) of
#user{} = User -> User;
false -> undefined
end.
-spec get_email(user() | undefined) -> string().
get_email(undefined) ->
"no email";
get_email(#user{email=Email}) ->
Email.
Why this translation:
undefined atom{ok, Value} / error patternRoc:
squares : List I64 -> List I64
squares = \list ->
List.map(list, \x -> x * x)
evens : List I64 -> List I64
evens = \list ->
List.keepIf(list, \x -> x % 2 == 0)
pairs : List a, List b -> List (a, b)
pairs = \list1, list2 ->
List.joinMap(list1, \x ->
List.map(list2, \y -> (x, y))
)
Erlang:
-spec squares([integer()]) -> [integer()].
squares(List) ->
[X * X || X <- List].
-spec evens([integer()]) -> [integer()].
evens(List) ->
[X || X <- List, X rem 2 == 0].
-spec pairs([A], [B]) -> [{A, B}].
pairs(List1, List2) ->
[{X, Y} || X <- List1, Y <- List2].
Why this translation:
joinMap (flatMap) becomes nested comprehensionRoc has no built-in concurrency - it's platform-provided. Erlang's concurrency is core to the language via lightweight processes.
Roc:
# No processes - pure functions
processWork : Data -> Result
processWork = \data ->
transform(data)
# Platform provides Tasks
doWork : Data -> Task Result []
doWork = \data ->
Task.fromResult(processWork(data))
# Multiple concurrent tasks (platform-dependent)
doMultipleWork : List Data -> Task (List Result) []
doMultipleWork = \dataList ->
dataList
|> List.map(doWork)
|> Task.sequence
Erlang:
% Direct process spawning
process_work(Data) ->
transform(Data).
do_work(Data) ->
% Spawn process to do work
Self = self(),
spawn(fun() ->
Result = process_work(Data),
Self ! {result, Result}
end),
receive
{result, Result} -> Result
end.
% Multiple concurrent processes
do_multiple_work(DataList) ->
Self = self(),
% Spawn workers
_Pids = [spawn(fun() ->
Result = process_work(Data),
Self ! {result, Result}
end) || Data <- DataList],
% Collect results
[receive {result, R} -> R end || _ <- DataList].
Why this approach:
Roc:
# Hypothetical platform API
doWorkflow : Input -> Result Output [WorkerFailed, ValidationFailed]
doWorkflow = \input ->
validated = validate!(input)
processed = processData!(validated)
saved = saveResult!(processed)
Ok(saved)
Erlang:
% gen_server for stateful workflow
-module(workflow_server).
-behaviour(gen_server).
-export([start_link/0, execute/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
execute(Input) ->
gen_server:call(?MODULE, {execute, Input}).
init([]) ->
{ok, #{}}.
handle_call({execute, Input}, _From, State) ->
Result = do_workflow(Input),
{reply, Result, State}.
do_workflow(Input) ->
case validate(Input) of
{ok, Validated} ->
case process_data(Validated) of
{ok, Processed} ->
save_result(Processed);
{error, Reason} ->
{error, {worker_failed, Reason}}
end;
{error, Reason} ->
{error, {validation_failed, Reason}}
end.
% ... other callbacks
Why this translation:
Roc:
# Platform handles process-level concerns
# Application code doesn't deal with supervision
Erlang:
-module(my_supervisor).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
SupFlags = #{
strategy => one_for_one,
intensity => 5,
period => 60
},
ChildSpecs = [
#{
id => worker1,
start => {worker_module, start_link, []},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [worker_module]
}
],
{ok, {SupFlags, ChildSpecs}}.
Why this approach:
Roc Philosophy:
# Make errors explicit with Result types
processData : Data -> Result Success [ValidationErr, TransformErr, SaveErr]
processData = \data ->
validated = validate!(data)
transformed = transform!(validated)
saved = save!(transformed)
Ok(saved)
Erlang Philosophy:
% Let it crash - supervisor will restart
process_data(Data) ->
Validated = validate(Data), % May crash
Transformed = transform(Validated), % May crash
save(Transformed). % May crash
% Or use tagged tuples for recoverable errors
process_data_safe(Data) ->
case validate(Data) of
{ok, Validated} ->
case transform(Validated) of
{ok, Transformed} ->
save(Transformed);
{error, Reason} ->
{error, {transform_error, Reason}}
end;
{error, Reason} ->
{error, {validation_error, Reason}}
end.
Key Differences:
| Roc Pattern | Erlang Pattern | Notes |
|---|---|---|
Err(error) | {error, Reason} or crash | Choose based on recoverability |
Ok(value) | {ok, Value} | Standard success pattern |
Result.try (!) | case or crash | Chain error handling |
| Tag unions for errors | Atoms or tagged tuples | Multiple error types |
| Platform retry | Explicit retry or supervisor restart | No automatic retry |
Roc:
interface Calculator
exposes [Result, add, subtract]
imports []
Result : [Ok F64, Err [InvalidInput]]
add : F64, F64 -> Result
add = \a, b -> Ok(a + b)
subtract : F64, F64 -> Result
subtract = \a, b -> Ok(a - b)
Erlang:
-module(calculator).
-export([add/2, subtract/2]).
-export_type([result/0]).
-type result() :: {ok, float()} | {error, invalid_input}.
-spec add(float(), float()) -> result().
add(A, B) -> {ok, A + B}.
-spec subtract(float(), float()) -> result().
subtract(A, B) -> {ok, A - B}.
Why this translation:
interface becomes -moduleexposes becomes -export-export_type-typeRoc Application:
my-app/
├── main.roc # Entry point
├── Worker.roc # Worker module
└── Types.roc # Shared types
Erlang OTP Application:
my_app/
├── src/
│ ├── my_app.app.src # Application resource file
│ ├── my_app_app.erl # Application behavior
│ ├── my_app_sup.erl # Top-level supervisor
│ └── my_worker.erl # Worker gen_server
├── include/
│ └── my_app.hrl # Header files
└── rebar.config # Build configuration
Key Differences:
Roc:
app [main] {
pf: platform "https://github.com/roc-lang/basic-cli/..."
}
import pf.Stdout
import pf.Task exposing [Task]
main : Task {} []
main =
Stdout.line!("Hello, World!")
Erlang:
% my_app.app.src
{application, my_app, [
{description, "My Application"},
{vsn, "1.0.0"},
{registered, []},
{mod, {my_app_app, []}},
{applications, [kernel, stdlib]},
{env, []}
]}.
% my_app_app.erl
-module(my_app_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_Type, _Args) ->
io:format("Hello, World!~n"),
my_app_sup:start_link().
stop(_State) ->
ok.
% my_app_sup.erl
-module(my_app_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
SupFlags = #{strategy => one_for_one, intensity => 5, period => 60},
ChildSpecs = [],
{ok, {SupFlags, ChildSpecs}}.
Why this approach:
Roc:
add : I64, I64 -> I64
add = \a, b -> a + b
expect add(2, 3) == 5
expect add(0, 0) == 0
expect add(-1, 1) == 0
expect
result = add(2, 3)
result == 5
Erlang:
-module(calculator_tests).
-include_lib("eunit/include/eunit.hrl").
add_test() ->
?assertEqual(5, calculator:add(2, 3)).
add_zero_test() ->
?assertEqual(0, calculator:add(0, 0)).
add_negative_test() ->
?assertEqual(0, calculator:add(-1, 1)).
add_complex_test() ->
Result = calculator:add(2, 3),
?assertEqual(5, Result).
Why this translation:
_test?assertEqual provides assertionrebar3 eunitTrying to keep everything pure: Erlang embraces processes and side effects. Don't avoid them.
Not using OTP behaviors: Raw processes are harder to supervise. Use gen_server, gen_statem, supervisor.
Ignoring dynamic typing: Erlang is dynamically typed. Use specs and dialyzer, but don't expect compile-time type safety.
Over-engineering for fault tolerance: Not everything needs to be a process. Pure functions can stay functions.
Translating Result chains literally: Erlang's error handling is often simpler with let-it-crash. Only use explicit error handling when recovery is possible.
Forgetting about distribution: Erlang makes distribution easy. Consider whether your application should be distributed.
Not thinking about hot code reload: Erlang supports hot code reloading. Design with code_change in mind.
Assuming immutability everywhere: While Erlang data is immutable, process state is mutable via message passing.
Missing the message passing idiom: Don't just call functions - think about message protocols between processes.
Not using ETS for shared state: For shared read-heavy state, ETS is more efficient than message passing.
| Purpose | Roc | Erlang | Notes |
|---|---|---|---|
| Build tool | roc CLI | rebar3 | Erlang has mature build ecosystem |
| Package manager | Platform URLs | hex.pm + rebar3 | Hex is standard package registry |
| Testing | roc test | EUnit, CT, PropEr | Multiple testing frameworks |
| REPL | - | erl shell | Interactive development |
| Formatter | roc format | erlfmt, rebar3 fmt | Multiple formatters available |
| Documentation | Comments | EDoc | Generate HTML docs |
| Debugger | - | debugger, observer | GUI debugging tools |
| Profiling | - | fprof, eprof | Multiple profiling tools |
| Static analysis | Type system | dialyzer | Gradual typing via specs |
| Release building | Platform | relx (via rebar3) | Production releases |
Before (Roc):
interface Color
exposes [Color, toString]
imports []
Color : [Red, Green, Blue, Rgb U8 U8 U8]
toString : Color -> Str
toString = \color ->
when color is
Red -> "Red"
Green -> "Green"
Blue -> "Blue"
Rgb(r, g, b) -> "RGB(\(Num.toStr(r)), \(Num.toStr(g)), \(Num.toStr(b)))"
After (Erlang):
-module(color).
-export([to_string/1]).
-type color() :: red | green | blue | {rgb, byte(), byte(), byte()}.
-spec to_string(color()) -> string().
to_string(red) -> "Red";
to_string(green) -> "Green";
to_string(blue) -> "Blue";
to_string({rgb, R, G, B}) ->
io_lib:format("RGB(~p, ~p, ~p)", [R, G, B]).
Before (Roc):
interface BankAccount
exposes [Account, new, deposit, withdraw, balance]
imports []
Account : { balance : U64 }
new : Account
new = { balance: 0 }
deposit : Account, U64 -> Result Account [InvalidAmount]
deposit = \account, amount ->
if amount > 0 then
Ok({ account & balance: account.balance + amount })
else
Err(InvalidAmount)
withdraw : Account, U64 -> Result Account [InvalidAmount, InsufficientFunds]
withdraw = \account, amount ->
if amount == 0 then
Err(InvalidAmount)
else if amount > account.balance then
Err(InsufficientFunds)
else
Ok({ account & balance: account.balance - amount })
balance : Account -> U64
balance = \account ->
account.balance
After (Erlang):
-module(bank_account).
-behaviour(gen_server).
%% API
-export([start_link/0, deposit/1, withdraw/1, balance/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {
balance = 0 :: non_neg_integer()
}).
%%% API
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
deposit(Amount) ->
gen_server:call(?MODULE, {deposit, Amount}).
withdraw(Amount) ->
gen_server:call(?MODULE, {withdraw, Amount}).
balance() ->
gen_server:call(?MODULE, balance).
%%% gen_server callbacks
init([]) ->
{ok, #state{}}.
handle_call({deposit, Amount}, _From, State) when Amount > 0 ->
NewState = State#state{balance = State#state.balance + Amount},
{reply, {ok, NewState#state.balance}, NewState};
handle_call({deposit, _}, _From, State) ->
{reply, {error, invalid_amount}, State};
handle_call({withdraw, Amount}, _From, State) when Amount =< 0 ->
{reply, {error, invalid_amount}, State};
handle_call({withdraw, Amount}, _From, State) when Amount > State#state.balance ->
{reply, {error, insufficient_funds}, State};
handle_call({withdraw, Amount}, _From, State) ->
NewState = State#state{balance = State#state.balance - Amount},
{reply, {ok, NewState#state.balance}, NewState};
handle_call(balance, _From, State) ->
{reply, State#state.balance, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Before (Roc):
interface TaskQueue
exposes [Queue, empty, addTask, getNext, count]
imports []
Queue task : {
tasks : List task,
processed : U64,
}
empty : Queue task
empty = {
tasks: [],
processed: 0,
}
addTask : Queue task, task -> Queue task
addTask = \queue, task ->
{ queue & tasks: List.append(queue.tasks, task) }
getNext : Queue task -> Result (Queue task, task) [Empty]
getNext = \queue ->
when queue.tasks is
[] -> Err(Empty)
[first, ..rest] ->
newQueue = {
tasks: rest,
processed: queue.processed + 1,
}
Ok((newQueue, first))
count : Queue task -> { pending : U64, processed : U64 }
count = \queue ->
{
pending: List.len(queue.tasks),
processed: queue.processed,
}
After (Erlang):
%% task_queue.erl - gen_server implementation
-module(task_queue).
-behaviour(gen_server).
%% API
-export([start_link/0, add_task/1, get_next/0, count/0, stop/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {
tasks = [] :: [term()],
processed = 0 :: non_neg_integer()
}).
%%% API Functions
-spec start_link() -> {ok, pid()} | {error, term()}.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
-spec add_task(term()) -> ok.
add_task(Task) ->
gen_server:cast(?MODULE, {add, Task}).
-spec get_next() -> {ok, term()} | {error, empty}.
get_next() ->
gen_server:call(?MODULE, get_next).
-spec count() -> #{pending => non_neg_integer(), processed => non_neg_integer()}.
count() ->
gen_server:call(?MODULE, count).
-spec stop() -> ok.
stop() ->
gen_server:stop(?MODULE).
%%% gen_server Callbacks
init([]) ->
{ok, #state{}}.
handle_call(get_next, _From, #state{tasks = []} = State) ->
{reply, {error, empty}, State};
handle_call(get_next, _From, #state{tasks = [First | Rest], processed = P} = State) ->
NewState = State#state{
tasks = Rest,
processed = P + 1
},
{reply, {ok, First}, NewState};
handle_call(count, _From, #state{tasks = Tasks, processed = P} = State) ->
Result = #{
pending => length(Tasks),
processed => P
},
{reply, Result, State}.
handle_cast({add, Task}, #state{tasks = Tasks} = State) ->
NewState = State#state{tasks = Tasks ++ [Task]},
{noreply, NewState};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% task_queue_sup.erl - Supervisor
-module(task_queue_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
SupFlags = #{
strategy => one_for_one,
intensity => 5,
period => 60
},
ChildSpecs = [
#{
id => task_queue,
start => {task_queue, start_link, []},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [task_queue]
}
],
{ok, {SupFlags, ChildSpecs}}.
%% task_queue_app.erl - Application
-module(task_queue_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_Type, _Args) ->
task_queue_sup:start_link().
stop(_State) ->
ok.
Static Type Safety: Roc's compile-time type checking doesn't exist in Erlang. Use specs and dialyzer for gradual typing.
Platform Abstraction: Roc platforms hide implementation details. In Erlang, you must design process architecture explicitly.
Pure Functional Guarantees: Roc guarantees purity. Erlang processes have side effects - embrace it.
Zero-Cost Abstractions: Roc compiles to native code. Erlang runs on BEAM VM with different performance characteristics.
Automatic Memory Management: Roc has predictable memory, Erlang uses per-process GC.
For more examples and patterns, see:
meta-convert-dev - Foundational patterns with cross-language examplesconvert-erlang-roc - Reverse conversion (Erlang → Roc)lang-roc-dev - Roc development patterns and platform modellang-erlang-dev - Erlang development patterns and OTPCross-cutting pattern skills:
patterns-concurrency-dev - Pure functions vs actors vs tasks across languagespatterns-serialization-dev - Encoding/decoding across languagespatterns-metaprogramming-dev - Compile-time vs runtime code generationxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience