xiaohongshu
xiaohongshu
convert-elm-erlang
Convert Elm code to idiomatic Erlang/OTP. Use when migrating Elm frontend applications to Erlang backend services, translating Elm patterns to OTP behaviors, or refactoring functional code to BEAM VM. Extends meta-convert-dev with Elm-to-Erlang specific patterns.
Full skill instructions
Convert Elm code to idiomatic Erlang/OTP. This skill extends meta-convert-dev with Elm-to-Erlang specific type mappings, idiom translations, and architectural patterns for moving from pure functional frontend code to fault-tolerant backend systems.
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-elm-devlang-erlang-devconvert-erlang-elm| Elm | Erlang | Notes |
|---|---|---|
String | binary() or list() | Use binaries for efficiency |
Int | integer() | Direct mapping |
Float | float() | Direct mapping |
Bool | true / false | Atoms in Erlang |
List a | [a] | Direct mapping |
Maybe a | {ok, a} / undefined / error | Tagged tuples |
Result e a | {ok, a} / {error, e} | Standard Erlang convention |
type alias | -record() or map() | Records for structure |
type Msg | Message patterns | Process mailbox messages |
Model | gen_server state | OTP state management |
update function | handle_call/cast | OTP callbacks |
Cmd Msg | spawn / ! / gen_server:call | Process operations |
Sub Msg | receive / timers | Message reception |
| Elm | Erlang | Notes |
|---|---|---|
String | binary() | UTF-8 binary: <<"Hello">> |
String | string() | List of codepoints: "Hello" (less efficient) |
Int | integer() | Arbitrary precision |
Float | float() | IEEE 754 double precision |
Bool | true / false | Atoms, not a separate type |
Char | integer() | Unicode codepoint |
() (unit) | ok / undefined | Atom for no value |
| Elm | Erlang | Notes |
|---|---|---|
List a | [a] | Linked list |
Array a | array:array(a) | Fixed-size arrays (rare) |
Dict k v | maps:map(k, v) | Modern maps (Erlang 17+) |
Dict k v | dict:dict(k, v) | Legacy dict module |
Set a | sets:set(a) | Set data structure |
(a, b) | {a, b} | Tuple (fixed size) |
(a, b, c) | {a, b, c} | Tuple with 3+ elements |
| Elm | Erlang | Notes |
|---|---|---|
type alias User = { name : String, age : Int } | -record(user, {name :: binary(), age :: integer()}). | Record with type specs |
type alias User = { name : String, age : Int } | #{name => binary(), age => integer()} | Map (more flexible) |
type Msg = Increment | Decrement | Message patterns in receive | Patterns, not types |
type Result e a = Ok a | Err e | {ok, a} | {error, e} | Tagged tuples |
type Maybe a = Just a | Nothing | {ok, a} | undefined | {error, not_found} | Multiple conventions |
Elm:
type TrafficLight
= Red
| Yellow
| Green
canGo : TrafficLight -> Bool
canGo light =
case light of
Green -> True
Yellow -> False
Red -> False
Erlang:
% Define as atoms or tagged tuples
can_go(green) -> true;
can_go(yellow) -> false;
can_go(red) -> false.
% Or with more structure
can_go(Light) ->
case Light of
green -> true;
yellow -> false;
red -> false
end.
Why this translation:
Elm:
type alias Model = { count : Int }
type Msg
= Increment
| Decrement
init : () -> ( Model, Cmd Msg )
init _ = ( { count = 0 }, Cmd.none )
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Increment -> ( { model | count = model.count + 1 }, Cmd.none )
Decrement -> ( { model | count = model.count - 1 }, Cmd.none )
Erlang:
-module(counter).
-behaviour(gen_server).
-export([start_link/0, increment/0, decrement/0, get_count/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-record(state, {count = 0 :: integer()}).
%%% API
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
increment() ->
gen_server:cast(?MODULE, increment).
decrement() ->
gen_server:cast(?MODULE, decrement).
get_count() ->
gen_server:call(?MODULE, get_count).
%%% Callbacks
init([]) ->
{ok, #state{count = 0}}.
handle_call(get_count, _From, State) ->
{reply, State#state.count, State}.
handle_cast(increment, State = #state{count = Count}) ->
{noreply, State#state{count = Count + 1}};
handle_cast(decrement, State = #state{count = Count}) ->
{noreply, State#state{count = Count - 1}}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Why this translation:
Elm:
findUser : Int -> Maybe User
findUser id =
if id == 1 then
Just { name = "Alice", age = 30 }
else
Nothing
name : String
name =
findUser 1
|> Maybe.map .name
|> Maybe.withDefault "Anonymous"
Erlang:
find_user(Id) ->
case Id of
1 -> {ok, #{name => <<"Alice">>, age => 30}};
_ -> {error, not_found}
end.
% Using case for pattern matching
get_name() ->
case find_user(1) of
{ok, User} -> maps:get(name, User);
{error, _} -> <<"Anonymous">>
end.
% Or with function clauses
get_name_clause({ok, User}) -> maps:get(name, User);
get_name_clause({error, _}) -> <<"Anonymous">>.
Why this translation:
Nothing → {error, Reason} or undefinedJust value → {ok, Value}{ok, Result} for success, {error, Reason} for failureElm:
result : Int
result =
[1, 2, 3, 4, 5]
|> List.filter (\x -> x > 2)
|> List.map (\x -> x * 2)
|> List.foldl (+) 0
Erlang:
result() ->
lists:foldl(
fun(X, Acc) -> X + Acc end,
0,
lists:map(
fun(X) -> X * 2 end,
lists:filter(
fun(X) -> X > 2 end,
[1, 2, 3, 4, 5]
)
)
).
% Or with list comprehensions (more idiomatic)
result_comprehension() ->
Sum = fun(List) -> lists:sum(List) end,
Sum([X * 2 || X <- [1, 2, 3, 4, 5], X > 2]).
Why this translation:
|> → nested function calls or list comprehensionslists: module provides functional primitivesElm:
type alias User = { name : String, age : Int, email : String }
user : User
user = { name = "Alice", age = 30, email = "[email protected]" }
updatedUser : User
updatedUser = { user | age = 31 }
Erlang:
-record(user, {
name :: binary(),
age :: integer(),
email :: binary()
}).
user() ->
#user{name = <<"Alice">>, age = 30, email = <<"[email protected]">>}.
updated_user() ->
User = user(),
User#user{age = 31}.
% Or with maps
user_map() ->
#{name => <<"Alice">>, age => 30, email => <<"[email protected]">>}.
updated_user_map() ->
User = user_map(),
User#{age := 31}. % := for updating existing key
Why this translation:
Elm:
type Msg
= GotUsers (Result Http.Error (List User))
getUsers : Cmd Msg
getUsers =
Http.get
{ url = "https://api.example.com/users"
, expect = Http.expectJson GotUsers usersDecoder
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FetchUsers ->
( { model | loading = True }, getUsers )
GotUsers result ->
case result of
Ok users -> ( { model | users = users, loading = False }, Cmd.none )
Err error -> ( { model | error = Just error, loading = False }, Cmd.none )
Erlang:
-module(user_fetcher).
-behaviour(gen_server).
handle_cast(fetch_users, State) ->
% Spawn async HTTP request
Self = self(),
spawn(fun() ->
case httpc:request(get, {"https://api.example.com/users", []}, [], []) of
{ok, {{_, 200, _}, _, Body}} ->
Users = decode_users(Body),
Self ! {users_fetched, {ok, Users}};
{error, Reason} ->
Self ! {users_fetched, {error, Reason}}
end
end),
{noreply, State#{loading => true}}.
handle_info({users_fetched, {ok, Users}}, State) ->
{noreply, State#{users => Users, loading => false, error => undefined}};
handle_info({users_fetched, {error, Reason}}, State) ->
{noreply, State#{error => Reason, loading => false}}.
Why this translation:
Elm:
subscriptions : Model -> Sub Msg
subscriptions model =
Time.every 1000 Tick -- Every second
type Msg
= Tick Time.Posix
Erlang:
init([]) ->
% Set up recurring timer
{ok, TRef} = timer:send_interval(1000, self(), tick),
{ok, #{timer_ref => TRef}}.
handle_info(tick, State) ->
% Handle timer tick
NewState = do_periodic_work(State),
{noreply, NewState};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #{timer_ref := TRef}) ->
timer:cancel(TRef),
ok.
Why this translation:
Elm Approach (No Runtime Errors):
-- Elm: Everything must be handled at compile time
parseAge : String -> Result String Int
parseAge str =
case String.toInt str of
Just age ->
if age >= 0 then
Ok age
else
Err "Age must be non-negative"
Nothing ->
Err "Not a valid number"
Erlang Approach (Let It Crash):
% Erlang: Let supervisor restart on invalid input
parse_age(Str) ->
Age = list_to_integer(Str), % Crashes on invalid input
true = Age >= 0, % Crashes if negative
Age.
% Or with explicit error handling when needed
parse_age_safe(Str) ->
try list_to_integer(Str) of
Age when Age >= 0 -> {ok, Age};
Age -> {error, {negative_age, Age}}
catch
error:badarg -> {error, not_a_number}
end.
Translation Strategy:
{ok, Value} / {error, Reason} for APIs{ok, Value} / undefined / {error, not_found}Elm:
processUser : String -> Result String ProcessedUser
processUser input =
parseJson input
|> Result.andThen validateUser
|> Result.andThen enrichUser
|> Result.map processUser
Erlang:
process_user(Input) ->
case parse_json(Input) of
{ok, Json} ->
case validate_user(Json) of
{ok, User} ->
case enrich_user(User) of
{ok, Enriched} ->
{ok, process_user_data(Enriched)};
{error, Reason} -> {error, Reason}
end;
{error, Reason} -> {error, Reason}
end;
{error, Reason} -> {error, Reason}
end.
% Or with helper for chaining
chain(Value, []) -> {ok, Value};
chain({ok, Value}, [F | Rest]) ->
chain(F(Value), Rest);
chain({error, Reason}, _) ->
{error, Reason}.
process_user_chain(Input) ->
chain(parse_json(Input), [
fun validate_user/1,
fun enrich_user/1,
fun(U) -> {ok, process_user_data(U)} end
]).
Conceptual Mapping:
| Elm Concept | Erlang Equivalent | Notes |
|---|---|---|
| Elm Runtime manages concurrency | You spawn and manage processes | Explicit control |
| Cmd.batch | Multiple spawn/cast calls | Parallel operations |
| Cmd.none | Don't spawn, synchronous | Stay in current process |
| update guaranteed sequential | gen_server callbacks sequential | OTP guarantees |
| Multiple Subs | Multiple receive patterns | Pattern match messages |
Elm:
-- Runtime handles concurrency automatically
update msg model =
case msg of
FetchAll ->
( { model | loading = True }
, Cmd.batch
[ Http.get { url = "/api/users", expect = Http.expectJson GotUsers decoder }
, Http.get { url = "/api/posts", expect = Http.expectJson GotPosts decoder }
, Http.get { url = "/api/comments", expect = Http.expectJson GotComments decoder }
]
)
Erlang:
handle_cast(fetch_all, State) ->
Self = self(),
% Spawn three concurrent requests
spawn_link(fun() -> fetch_and_send(Self, "/api/users", users) end),
spawn_link(fun() -> fetch_and_send(Self, "/api/posts", posts) end),
spawn_link(fun() -> fetch_and_send(Self, "/api/comments", comments) end),
{noreply, State#{loading => true, pending => 3}}.
fetch_and_send(Parent, Url, Type) ->
Result = httpc:request(get, {Url, []}, [], []),
Parent ! {fetched, Type, Result}.
handle_info({fetched, Type, Result}, State = #{pending := Pending}) ->
NewState = State#{Type => Result, pending => Pending - 1},
case maps:get(pending, NewState) of
0 -> {noreply, NewState#{loading => false}};
_ -> {noreply, NewState}
end.
Elm Application Structure:
src/
Main.elm -- Entry point (Browser.element)
Types.elm -- Shared types (Model, Msg)
Api.elm -- HTTP functions
Page/
Home.elm -- Page modules
Erlang/OTP Application Structure:
src/
myapp.app.src -- Application metadata
myapp_app.erl -- Application behavior (entry point)
myapp_sup.erl -- Top-level supervisor
myapp_server.erl -- Main gen_server (Model + update)
myapp_api.erl -- HTTP client functions
myapp_worker.erl -- Worker processes
Elm: Single runtime, no crashes Erlang: Supervision tree for fault tolerance
-module(myapp_sup).
-behaviour(supervisor).
init([]) ->
SupFlags = #{
strategy => one_for_one,
intensity => 5,
period => 60
},
ChildSpecs = [
#{
id => main_server,
start => {myapp_server, start_link, []},
restart => permanent,
shutdown => 5000,
type => worker
},
#{
id => worker_pool_sup,
start => {myapp_worker_sup, start_link, []},
restart => permanent,
shutdown => infinity,
type => supervisor
}
],
{ok, {SupFlags, ChildSpecs}}.
Elm:
import Json.Decode as Decode exposing (Decoder)
type alias User = { name : String, age : Int, email : String }
userDecoder : Decoder User
userDecoder =
Decode.map3 User
(Decode.field "name" Decode.string)
(Decode.field "age" Decode.int)
(Decode.field "email" Decode.string)
Erlang:
-record(user, {
name :: binary(),
age :: integer(),
email :: binary()
}).
% Using jsone library
decode_user(Json) ->
case jsone:decode(Json, [{object_format, map}]) of
#{<<"name">> := Name, <<"age">> := Age, <<"email">> := Email} ->
{ok, #user{name = Name, age = Age, email = Email}};
_ ->
{error, invalid_json}
end.
% Or with jiffy
decode_user_jiffy(Json) ->
{Props} = jiffy:decode(Json),
Name = proplists:get_value(<<"name">>, Props),
Age = proplists:get_value(<<"age">>, Props),
Email = proplists:get_value(<<"email">>, Props),
#user{name = Name, age = Age, email = Email}.
Elm Encoders → Erlang Encoding:
Elm:
import Json.Encode as Encode
encodeUser : User -> Encode.Value
encodeUser user =
Encode.object
[ ( "name", Encode.string user.name )
, ( "age", Encode.int user.age )
, ( "email", Encode.string user.email )
]
Erlang:
encode_user(#user{name = Name, age = Age, email = Email}) ->
jsone:encode(#{
<<"name">> => Name,
<<"age">> => Age,
<<"email">> => Email
}).
Problem: Elm catches all errors at compile time; Erlang relies on runtime checks and supervision.
Elm:
-- Compiler forces you to handle all cases
processResult : Result Error Value -> String
processResult result =
case result of
Ok value -> "Success"
Err error -> "Failed" -- MUST handle or won't compile
Erlang:
% No compile-time exhaustiveness checking
process_result({ok, _Value}) -> "Success".
% Forgot {error, _} case → runtime crash (but supervisor restarts)
Solution: Use dialyzer for static analysis, embrace let-it-crash philosophy with supervision.
Problem: Elm's String is always Unicode text; Erlang has both binaries and lists.
Bad:
% Mixing strings and binaries
Name = "Alice", % List of integers
Email = <<"[email protected]">>, % Binary
Combined = Name ++ Email. % ERROR: can't concatenate list and binary
Good:
% Be consistent: use binaries for text
Name = <<"Alice">>,
Email = <<"[email protected]">>,
Combined = <<Name/binary, <<" - ">>/binary, Email/binary>>.
Problem: Translating Elm's explicit error handling to defensive try-catch everywhere.
Bad:
% Over-defensive (not idiomatic Erlang)
process_data(Data) ->
try
Step1 = validate(Data),
Step2 = transform(Step1),
Step3 = save(Step2),
{ok, Step3}
catch
_:_ -> {error, something_failed}
end.
Good:
% Let it crash in workers, handle errors at API boundaries
process_data(Data) ->
Step1 = validate(Data), % Crash if invalid
Step2 = transform(Step1), % Crash if transform fails
save(Step2). % Crash if save fails
% Supervisor will restart this process if it crashes
Problem: Writing raw process loops instead of using gen_server, gen_statem.
Bad:
% Reimplementing gen_server
loop(State) ->
receive
{From, get} ->
From ! {self(), State},
loop(State);
{From, set, NewState} ->
From ! {self(), ok},
loop(NewState)
end.
Good:
% Use OTP behaviors
-behaviour(gen_server).
handle_call(get, _From, State) ->
{reply, State, State};
handle_call({set, NewState}, _From, _State) ->
{reply, ok, NewState}.
Problem: Not using Erlang's powerful binary pattern matching for parsing.
Elm (can't do this):
-- Must use String functions
parseHeader : String -> Maybe Header
Erlang (idiomatic):
% Binary pattern matching is idiomatic
parse_header(<<Type:8, Length:16, Rest/binary>>) ->
<<Payload:Length/binary, Remaining/binary>> = Rest,
{ok, {Type, Payload}, Remaining}.
| Tool | Purpose | Notes |
|---|---|---|
rebar3 | Build tool | Like Elm's build system |
dialyzer | Static analysis | Catches type errors |
elvis | Style checker | Code linting |
xref | Cross-reference | Find unused functions |
eunit | Unit testing | Like elm-test |
common_test | Integration testing | Full test suites |
proper | Property-based testing | Like elm-explorations/test |
jsone / jiffy | JSON parsing | External libraries |
hackney / gun | HTTP client | Like elm/http |
Elm:
type alias Model = { count : Int }
type Msg = Increment | Decrement | Reset
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Increment -> ( { model | count = model.count + 1 }, Cmd.none )
Decrement -> ( { model | count = model.count - 1 }, Cmd.none )
Reset -> ( { count = 0 }, Cmd.none )
Erlang:
-module(counter).
-behaviour(gen_server).
-export([start_link/0, increment/0, decrement/0, reset/0, get/0]).
-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, [], []).
increment() -> gen_server:cast(?MODULE, increment).
decrement() -> gen_server:cast(?MODULE, decrement).
reset() -> gen_server:cast(?MODULE, reset).
get() -> gen_server:call(?MODULE, get).
init([]) ->
{ok, 0}.
handle_call(get, _From, Count) ->
{reply, Count, Count}.
handle_cast(increment, Count) ->
{noreply, Count + 1};
handle_cast(decrement, Count) ->
{noreply, Count - 1};
handle_cast(reset, _Count) ->
{noreply, 0}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_Old, State, _Extra) -> {ok, State}.
Elm:
type alias User = { id : Int, name : String, email : String }
type Msg
= FetchUsers
| GotUsers (Result Http.Error (List User))
| DeleteUser Int
| UserDeleted (Result Http.Error ())
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
FetchUsers ->
( { model | loading = True }, fetchUsers )
GotUsers result ->
case result of
Ok users -> ( { model | users = users, loading = False }, Cmd.none )
Err error -> ( { model | error = Just error, loading = False }, Cmd.none )
DeleteUser id ->
( model, deleteUser id )
UserDeleted result ->
case result of
Ok _ -> ( model, fetchUsers )
Err error -> ( { model | error = Just error }, Cmd.none )
Erlang:
-module(user_manager).
-behaviour(gen_server).
-record(state, {
users = [] :: [map()],
loading = false :: boolean(),
error = undefined :: undefined | binary()
}).
%% API
-export([start_link/0, fetch_users/0, delete_user/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, [], []).
fetch_users() ->
gen_server:cast(?MODULE, fetch_users).
delete_user(Id) ->
gen_server:cast(?MODULE, {delete_user, Id}).
init([]) ->
{ok, #state{}}.
handle_cast(fetch_users, State) ->
Self = self(),
spawn_link(fun() ->
Result = fetch_users_http(),
Self ! {users_fetched, Result}
end),
{noreply, State#state{loading = true}};
handle_cast({delete_user, Id}, State) ->
Self = self(),
spawn_link(fun() ->
Result = delete_user_http(Id),
Self ! {user_deleted, Result}
end),
{noreply, State}.
handle_info({users_fetched, {ok, Users}}, State) ->
{noreply, State#state{users = Users, loading = false, error = undefined}};
handle_info({users_fetched, {error, Reason}}, State) ->
{noreply, State#state{loading = false, error = format_error(Reason)}};
handle_info({user_deleted, {ok, _}}, State) ->
% Refetch users after deletion
gen_server:cast(self(), fetch_users),
{noreply, State};
handle_info({user_deleted, {error, Reason}}, State) ->
{noreply, State#state{error = format_error(Reason)}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Internal functions
fetch_users_http() ->
case httpc:request(get, {"https://api.example.com/users", []}, [], []) of
{ok, {{_, 200, _}, _, Body}} ->
{ok, jsone:decode(Body, [{object_format, map}])};
{error, Reason} ->
{error, Reason}
end.
delete_user_http(Id) ->
Url = "https://api.example.com/users/" ++ integer_to_list(Id),
case httpc:request(delete, {Url, []}, [], []) of
{ok, {{_, 204, _}, _, _}} ->
{ok, deleted};
{error, Reason} ->
{error, Reason}
end.
format_error(Reason) ->
list_to_binary(io_lib:format("~p", [Reason])).
Elm:
type State
= Idle
| Running { startTime : Time.Posix, elapsed : Float }
| Paused { elapsed : Float }
type Msg
= Start
| Stop
| Pause
| Tick Time.Posix
update : Msg -> State -> ( State, Cmd Msg )
update msg state =
case ( msg, state ) of
( Start, Idle ) ->
( Running { startTime = Time.millisToPosix 0, elapsed = 0 }, Cmd.none )
( Stop, Running _ ) ->
( Idle, Cmd.none )
( Stop, Paused _ ) ->
( Idle, Cmd.none )
( Pause, Running { elapsed } ) ->
( Paused { elapsed = elapsed }, Cmd.none )
( Start, Paused { elapsed } ) ->
( Running { startTime = Time.millisToPosix 0, elapsed = elapsed }, Cmd.none )
( Tick now, Running { startTime, elapsed } ) ->
let
delta = Time.posixToMillis now - Time.posixToMillis startTime
in
( Running { startTime = now, elapsed = elapsed + toFloat delta / 1000 }, Cmd.none )
_ ->
( state, Cmd.none )
subscriptions : State -> Sub Msg
subscriptions state =
case state of
Running _ ->
Time.every 100 Tick
_ ->
Sub.none
Erlang:
-module(stopwatch).
-behaviour(gen_statem).
-export([start_link/0, start/0, stop/0, pause/0, get_elapsed/0]).
-export([init/1, callback_mode/0, idle/3, running/3, paused/3, terminate/3]).
-record(data, {
start_time = undefined :: undefined | integer(),
elapsed = 0 :: float(),
timer_ref = undefined :: undefined | reference()
}).
%% API
start_link() ->
gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []).
start() -> gen_statem:cast(?MODULE, start).
stop() -> gen_statem:cast(?MODULE, stop).
pause() -> gen_statem:cast(?MODULE, pause).
get_elapsed() -> gen_statem:call(?MODULE, get_elapsed).
%% Callbacks
callback_mode() -> state_functions.
init([]) ->
{ok, idle, #data{}}.
%% State: idle
idle(cast, start, Data) ->
{ok, TRef} = timer:send_interval(100, tick),
{next_state, running, Data#data{
start_time = erlang:monotonic_time(millisecond),
elapsed = 0,
timer_ref = TRef
}};
idle({call, From}, get_elapsed, Data) ->
{keep_state, Data, [{reply, From, 0}]};
idle(_EventType, _Event, Data) ->
{keep_state, Data}.
%% State: running
running(cast, stop, Data = #data{timer_ref = TRef}) ->
timer:cancel(TRef),
{next_state, idle, #data{}};
running(cast, pause, Data = #data{timer_ref = TRef, elapsed = Elapsed}) ->
timer:cancel(TRef),
{next_state, paused, Data#data{timer_ref = undefined}};
running(info, tick, Data = #data{start_time = StartTime, elapsed = Elapsed}) ->
Now = erlang:monotonic_time(millisecond),
Delta = (Now - StartTime) / 1000.0,
{keep_state, Data#data{
start_time = Now,
elapsed = Elapsed + Delta
}};
running({call, From}, get_elapsed, Data = #data{elapsed = Elapsed}) ->
{keep_state, Data, [{reply, From, Elapsed}]};
running(_EventType, _Event, Data) ->
{keep_state, Data}.
%% State: paused
paused(cast, start, Data = #data{elapsed = Elapsed}) ->
{ok, TRef} = timer:send_interval(100, tick),
{next_state, running, Data#data{
start_time = erlang:monotonic_time(millisecond),
timer_ref = TRef
}};
paused(cast, stop, _Data) ->
{next_state, idle, #data{}};
paused({call, From}, get_elapsed, Data = #data{elapsed = Elapsed}) ->
{keep_state, Data, [{reply, From, Elapsed}]};
paused(_EventType, _Event, Data) ->
{keep_state, Data}.
terminate(_Reason, _State, #data{timer_ref = TRef}) when TRef =/= undefined ->
timer:cancel(TRef),
ok;
terminate(_Reason, _State, _Data) ->
ok.
meta-convert-dev - Foundational conversion patterns with cross-language exampleslang-elm-dev - Elm development patterns and The Elm Architecturelang-erlang-dev - Erlang/OTP fundamentals, processes, and behaviorspatterns-concurrency-dev - Concurrency patterns across languages (Cmd/Sub vs processes)patterns-serialization-dev - JSON handling across languages (decoders vs parsing)patterns-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