Erlang-specific library development patterns. Use when creating OTP libraries, designing public APIs with process patterns, configuring rebar3, managing application resources, publishing to Hex, or writing EDoc. Extends meta-library-dev with Erlang/OTP tooling and idioms.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add aRustyDev/agents --skill lang-erlang-library-dev --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Lang Erlang Library Dev?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/arustydev-lang-erlang-library-dev-agents)More formats (shields.io, HTML) on the badges page.
---
name: lang-erlang-library-dev
description: Erlang-specific library development patterns. Use when creating OTP libraries, designing public APIs with process patterns, configuring rebar3, managing application resources, publishing to Hex, or writing EDoc. Extends meta-library-dev with Erlang/OTP tooling and idioms.
---
# Erlang Library Development
Erlang-specific patterns for OTP library development. This skill extends `meta-library-dev` with Erlang/OTP tooling, process-oriented design, and ecosystem practices.
## This Skill Extends
- `meta-library-dev` - Foundational library patterns (API design, versioning, testing strategies)
- `lang-erlang-dev` - Core Erlang patterns (processes, OTP behaviors, supervision)
For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first. For foundational Erlang patterns, see lang-erlang-dev.
## This Skill Adds
- **Erlang tooling**: rebar3 configuration, .app.src files, application structure
- **Hex publishing**: Package metadata, versioning, documentation
- **Library idioms**: Public API design, behavior callbacks, application supervision
- **OTP conventions**: Application structure, configuration, resource management
- **EDoc**: Documentation generation, type specifications, function docs
- **Dialyzer**: Type analysis, PLT files, type specifications
## This Skill Does NOT Cover
- General library patterns - see `meta-library-dev`
- Core Erlang/OTP basics - see `lang-erlang-dev`
- Web frameworks - see framework-specific skills
- Distributed systems - see `lang-erlang-distributed-dev`
- Performance optimization - see `lang-erlang-performance-dev`
---
## Quick Reference
| Task | Command/Pattern |
|------|-----------------|
| New library app | `rebar3 new lib <name>` |
| New OTP library | `rebar3 new app <name>` |
| Compile | `rebar3 compile` |
| Test | `rebar3 eunit` or `rebar3 ct` |
| Type check | `rebar3 dialyzer` |
| Generate docs | `rebar3 edoc` |
| Publish (dry run) | `rebar3 hex publish --dry-run` |
| Publish to Hex | `rebar3 hex publish` |
| Shell with library | `rebar3 shell` |
| Run tests | `rebar3 do eunit, ct` |
---
## rebar3 Configuration
### Basic rebar.config
```erlang
{erl_opts, [
debug_info,
warnings_as_errors,
warn_export_all,
warn_unused_import,
warn_untyped_record
]}.
{deps, []}.
{project_plugins, [rebar3_hex, rebar3_ex_doc]}.
{hex, [
{doc, #{provider => ex_doc}}
]}.
{profiles, [
{test, [
{deps, [
{proper, "1.4.0"},
{meck, "0.9.2"}
]},
{erl_opts, [nowarn_export_all]}
]},
{prod, [
{erl_opts, [no_debug_info, warnings_as_errors]}
]}
]}.
{dialyzer, [
{warnings, [
unmatched_returns,
error_handling,
underspecs
]},
{plt_extra_apps, [ssl, crypto]}
]}.
{xref_checks, [
undefined_function_calls,
undefined_functions,
locals_not_used,
deprecated_function_calls,
deprecated_functions
]}.
{cover_enabled, true}.
{cover_opts, [verbose]}.
```
### Application Resource File (.app.src)
```erlang
{application, mylib,
[{description, "A library for doing X efficiently"},
{vsn, "0.1.0"},
{registered, []},
{applications, [
kernel,
stdlib
]},
{env, []},
{modules, []},
{licenses, ["Apache-2.0"]},
{links, [
{"GitHub", "https://github.com/username/mylib"},
{"Hex", "https://hex.pm/packages/mylib"}
]},
{build_tools, ["rebar3"]},
{files, [
"src",
"include",
"rebar.config",
"rebar.lock",
"README.md",
"LICENSE"
]}
]}.
```
### OTP Application (.app.src with supervisor)
```erlang
{application, mylib,
[{description, "OTP library with supervision tree"},
{vsn, "0.1.0"},
{registered, [mylib_sup]},
{mod, {mylib_app, []}},
{applications, [
kernel,
stdlib,
sasl
]},
{env, [
{pool_size, 10},
{timeout, 5000}
]},
{modules, []},
{licenses, ["MIT"]},
{links, [{"GitHub", "https://github.com/username/mylib"}]},
{build_tools, ["rebar3"]}
]}.
```
---
## Project Structure
### Simple Library (No Supervision)
```
mylib/
├── rebar.config
├── rebar.lock
├── README.md
├── LICENSE
├── src/
│ ├── mylib.app.src # Application resource file
│ ├── mylib.erl # Main public API module
│ ├── mylib_parser.erl # Internal module
│ └── mylib_types.hrl # Type definitions
├── include/ # Public header files
│ └── mylib.hrl
├── test/
│ ├── mylib_eunit.erl # EUnit tests
│ └── mylib_SUITE.erl # Common Test suite
├── doc/ # Generated by EDoc
│ └── overview.edoc # Documentation overview
└── priv/ # Static resources
└── templates/
```
### OTP Application Library
```
mylib/
├── rebar.config
├── src/
│ ├── mylib.app.src
│ ├── mylib_app.erl # Application behavior
│ ├── mylib_sup.erl # Root supervisor
│ ├── mylib.erl # Public API
│ ├── mylib_server.erl # gen_server worker
│ └── mylib_worker.erl # Worker process
├── include/
│ └── mylib.hrl
├── test/
│ ├── mylib_eunit.erl
│ ├── mylib_SUITE.erl
│ └── mylib_prop.erl # PropEr property tests
├── doc/
│ └── overview.edoc
└── priv/
└── config/
└── defaults.config
```
---
## Public API Design
### Module Organization
**Single entry point for users:**
```erlang
-module(mylib).
-export([
start/0,
stop/0,
process/1,
process/2,
format/1
]).
%% @doc Start the application
-spec start() -> ok | {error, term()}.
start() ->
application:ensure_all_started(mylib).
%% @doc Stop the application
-spec stop() -> ok.
stop() ->
application:stop(mylib).
%% @doc Process input with default options
-spec process(Input :: binary()) -> {ok, term()} | {error, term()}.
process(Input) ->
process(Input, #{}).
%% @doc Process input with custom options
-spec process(Input :: binary(), Options :: map()) ->
{ok, term()} | {error, term()}.
process(Input, Options) ->
mylib_parser:parse(Input, Options).
```
### Behavior Definition
**Define custom behaviors for extensibility:**
```erlang
-module(mylib_handler).
%% Behavior callback definitions
-callback init(Args :: term()) -> {ok, State :: term()} | {error, Reason :: term()}.
-callback handle_event(Event :: term(), State :: term()) ->
{ok, NewState :: term()} | {error, Reason :: term()}.
-callback terminate(Reason :: term(), State :: term()) -> ok.
-optional_callbacks([terminate/2]).
%% Export behavior
-export_type([handler/0]).
-type handler() :: module().
```
**Implement the behavior:**
```erlang
-module(my_custom_handler).
-behaviour(mylib_handler).
-export([init/1, handle_event/2, terminate/2]).
init(Args) ->
{ok, #{args => Args}}.
handle_event(Event, State) ->
io:format("Received: ~p~n", [Event]),
{ok, State}.
terminate(_Reason, _State) ->
ok.
```
### Options and Configuration
**Use maps for flexible options:**
```erlang
-module(mylib_config).
-export([parse_options/1, defaults/0]).
-type options() :: #{
timeout => pos_integer(),
retry => boolean(),
max_retries => pos_integer(),
format => json | xml | binary
}.
-export_type([options/0]).
%% @doc Default configuration
-spec defaults() -> options().
defaults() ->
#{
timeout => 5000,
retry => true,
max_retries => 3,
format => json
}.
%% @doc Merge user options with defaults
-spec parse_options(UserOpts :: map()) -> options().
parse_options(UserOpts) ->
maps:merge(defaults(), UserOpts).
```
---
## Type Specifications and Dialyzer
### Complete Type Specs
```erlang
-module(mylib_types).
%% Exported types
-export_type([
user_id/0,
user/0,
result/0,
error_reason/0
]).
%% Type definitions
-type user_id() :: non_neg_integer().
-type user() :: #{
id := user_id(),
name := binary(),
email := binary(),
created_at := calendar:datetime()
}.
-type error_reason() ::
not_found |
invalid_input |
timeout |
{internal_error, term()}.
-type result() :: {ok, term()} | {error, error_reason()}.
%% Records with types
-record(config, {
timeout :: pos_integer(),
max_connections :: pos_integer(),
handler :: module()
}).
-type config() :: #config{}.
-export_type([config/0]).
```
### Function Specifications
```erlang
-module(mylib_api).
%% @doc Create a new user
-spec create_user(Name :: binary(), Email :: binary()) ->
{ok, mylib_types:user()} | {error, mylib_types:error_reason()}.
create_user(Name, Email) when is_binary(Name), is_binary(Email) ->
UserId = generate_id(),
User = #{
id => UserId,
name => Name,
email => Email,
created_at => calendar:universal_time()
},
{ok, User}.
%% @doc Find user by ID
-spec find_user(mylib_types:user_id()) ->
{ok, mylib_types:user()} | {error, not_found}.
find_user(UserId) when is_integer(UserId), UserId >= 0 ->
case lookup_user(UserId) of
undefined -> {error, not_found};
User -> {ok, User}
end.
%% Internal function - no spec needed (dialyzer infers)
lookup_user(UserId) ->
ets:lookup(users_table, UserId).
```
### Dialyzer PLT Management
```erlang
% rebar.config
{dialyzer, [
{warnings, [
unmatched_returns,
error_handling,
underspecs,
unknown
]},
{plt_apps, all_deps},
{plt_extra_apps, [ssl, crypto, public_key]},
{plt_location, local},
{base_plt_location, global}
]}.
```
---
## EDoc Documentation
### Module Documentation
```erlang
%%% @doc Main API module for MyLib.
%%%
%%% This module provides the primary interface for working with MyLib.
%%% All operations are safe to use from multiple processes concurrently.
%%%
%%% == Quick Start ==
%%%
%%% ```
%%% 1> mylib:start().
%%% ok
%%% 2> {ok, Result} = mylib:process(<<"input">>).
%%% {ok, #{data => <<"processed">>}}
%%% '''
%%%
%%% == Configuration ==
%%%
%%% The application can be configured via application environment:
%%%
%%% ```
%%% {mylib, [
%%% {timeout, 10000},
%%% {pool_size, 20}
%%% ]}
%%% '''
%%%
%%% @end
-module(mylib).
-author("Your Name <your.email@example.com>").
-copyright("2025 Your Name").
-version("0.1.0").
```
### Function Documentation
```erlang
%%% @doc Process input data with options.
%%%
%%% This function processes the input binary according to the provided
%%% options and returns the result. Processing is done asynchronously
%%% in a worker pool.
%%%
%%% == Options ==
%%%
%%% <ul>
%%% <li>`timeout' - Maximum time in milliseconds (default: 5000)</li>
%%% <li>`format' - Output format: `json' or `binary' (default: json)</li>
%%% <li>`retry' - Whether to retry on failure (default: true)</li>
%%% </ul>
%%%
%%% == Examples ==
%%%
%%% ```
%%% %% Simple processing
%%% {ok, Result} = mylib:process(<<"data">>).
%%%
%%% %% With custom timeout
%%% {ok, Result} = mylib:process(<<"data">>, #{timeout => 10000}).
%%%
%%% %% Binary output format
%%% {ok, Binary} = mylib:process(<<"data">>, #{format => binary}).
%%% '''
%%%
%%% @see process/1
%%% @end
-spec process(Input :: binary(), Options :: map()) ->
{ok, term()} | {error, term()}.
process(Input, Options) ->
% Implementation
ok.
```
### Type Documentation
```erlang
%%% @type user_id() = non_neg_integer().
%%% Unique identifier for a user.
%%% @type user() = #{
%%% id := user_id(),
%%% name := binary(),
%%% email := binary(),
%%% created_at := calendar:datetime()
%%% }.
%%% User record containing all user information.
%%% @type error_reason() =
%%% not_found |
%%% invalid_input |
%%% timeout |
%%% {internal_error, term()}.
%%% Possible error reasons returned by library functions.
```
### Overview Documentation
Create `doc/overview.edoc`:
```erlang
@author Your Name <your.email@example.com>
@copyright 2025 Your Name
@version 0.1.0
@title MyLib - Efficient Data Processing Library
@doc
== Overview ==
MyLib is a high-performance library for processing data in Erlang/OTP
applications. It provides a simple, consistent API while leveraging
OTP principles for reliability and scalability.
== Features ==
<ul>
<li>Concurrent processing with worker pools</li>
<li>Automatic retries and error handling</li>
<li>Multiple output formats</li>
<li>Full type specifications</li>
<li>Comprehensive test coverage</li>
</ul>
== Installation ==
Add to your `rebar.config':
```
{deps, [
{mylib, "0.1.0"}
]}.
'''
== Quick Start ==
```
%% Start the application
ok = mylib:start().
%% Process some data
{ok, Result} = mylib:process(<<"input data">>).
%% Stop the application
ok = mylib:stop().
'''
@end
```
---
## Testing Patterns
### EUnit Tests
```erlang
-module(mylib_eunit).
-include_lib("eunit/include/eunit.hrl").
%%% Setup/Teardown
setup() ->
application:ensure_all_started(mylib).
cleanup(_) ->
application:stop(mylib).
%%% Test Fixtures
mylib_test_() ->
{setup,
fun setup/0,
fun cleanup/1,
[
{"Process valid input", fun test_process_valid/0},
{"Process invalid input", fun test_process_invalid/0},
{"Process with timeout", fun test_process_timeout/0}
]}.
%%% Tests
test_process_valid() ->
Input = <<"valid input">>,
{ok, Result} = mylib:process(Input),
?assertMatch(#{data := _}, Result).
test_process_invalid() ->
Input = <<"">>,
?assertEqual({error, invalid_input}, mylib:process(Input)).
test_process_timeout() ->
Input = <<"data">>,
Options = #{timeout => 1},
?assertMatch({error, timeout}, mylib:process(Input, Options)).
%%% Property-Based Tests
prop_never_crashes_test_() ->
{timeout, 60, fun() ->
?assert(proper:quickcheck(prop_no_crash(), [{numtests, 1000}]))
end}.
prop_no_crash() ->
?FORALL(Input, binary(),
case mylib:process(Input) of
{ok, _} -> true;
{error, _} -> true
end
).
```
### Common Test Suites
```erlang
-module(mylib_SUITE).
-include_lib("common_test/include/ct.hrl").
%% CT callbacks
-export([all/0, groups/0, init_per_suite/1, end_per_suite/1]).
-export([init_per_testcase/2, end_per_testcase/2]).
%% Test cases
-export([
test_basic_processing/1,
test_concurrent_processing/1,
test_error_handling/1
]).
%%% CT Callbacks
all() ->
[
{group, basic},
{group, concurrent},
{group, errors}
].
groups() ->
[
{basic, [parallel], [test_basic_processing]},
{concurrent, [parallel], [test_concurrent_processing]},
{errors, [sequence], [test_error_handling]}
].
init_per_suite(Config) ->
{ok, _} = application:ensure_all_started(mylib),
Config.
end_per_suite(_Config) ->
application:stop(mylib),
ok.
init_per_testcase(_TestCase, Config) ->
Config.
end_per_testcase(_TestCase, _Config) ->
ok.
%%% Test Cases
test_basic_processing(Config) ->
Input = <<"test data">>,
{ok, Result} = mylib:process(Input),
ct:log("Result: ~p", [Result]),
#{data := Data} = Result,
true = is_binary(Data).
test_concurrent_processing(Config) ->
Inputs = [<<"input", (integer_to_binary(N))/binary>> || N <- lists:seq(1, 100)],
Self = self(),
[spawn(fun() ->
{ok, _} = mylib:process(Input),
Self ! {done, N}
end) || {N, Input} <- lists:enumerate(Inputs)],
receive_n(100).
test_error_handling(Config) ->
{error, invalid_input} = mylib:process(<<>>),
{error, timeout} = mylib:process(<<"data">>, #{timeout => 1}).
%%% Helpers
receive_n(0) -> ok;
receive_n(N) ->
receive
{done, _} -> receive_n(N - 1)
after 5000 ->
ct:fail("Timeout waiting for concurrent operations")
end.
```
### PropEr Property Tests
```erlang
-module(mylib_prop).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
%%% Generators
user_id() ->
non_neg_integer().
user_name() ->
?LET(Len, range(1, 50),
?LET(Chars, vector(Len, range($a, $z)),
list_to_binary(Chars))).
valid_user() ->
?LET({Id, Name, Email}, {user_id(), user_name(), user_name()},
#{id => Id, name => Name, email => <<Email/binary, "@example.com">>}).
%%% Properties
prop_create_user_returns_user() ->
?FORALL({Name, Email}, {user_name(), user_name()},
begin
{ok, User} = mylib:create_user(Name, Email),
maps:is_key(id, User) andalso
maps:get(name, User) =:= Name andalso
maps:get(email, User) =:= Email
end).
prop_roundtrip_serialization() ->
?FORALL(User, valid_user(),
begin
Serialized = mylib:serialize(User),
{ok, Deserialized} = mylib:deserialize(Serialized),
User =:= Deserialized
end).
%%% EUnit wrapper
properties_test_() ->
{timeout, 120, [
?_assert(proper:quickcheck(prop_create_user_returns_user(), [{numtests, 100}])),
?_assert(proper:quickcheck(prop_roundtrip_serialization(), [{numtests, 100}]))
]}.
```
---
## Publishing to Hex
### Hex Package Configuration
Add to `rebar.config`:
```erlang
{project_plugins, [
rebar3_hex,
rebar3_ex_doc
]}.
{hex, [
{doc, #{provider => ex_doc}}
]}.
{ex_doc, [
{source_url, <<"https://github.com/username/mylib">>},
{extras, [<<"README.md">>, <<"LICENSE">>, <<"CHANGELOG.md">>]},
{main, <<"readme">>}
]}.
```
### Pre-publish Checklist
- [ ] Version bumped in `.app.src`
- [ ] CHANGELOG.md updated with version changes
- [ ] README.md is current and comprehensive
- [ ] All tests pass: `rebar3 do eunit, ct`
- [ ] Dialyzer passes: `rebar3 dialyzer`
- [ ] Documentation builds: `rebar3 edoc`
- [ ] Application metadata complete in `.app.src`
- [ ] License file(s) included
- [ ] No uncommitted changes
- [ ] Dry run succeeds: `rebar3 hex publish --dry-run`
### Publishing Commands
```bash
# Generate documentation
rebar3 hex docs
# Dry run to check package
rebar3 hex publish --dry-run
# Actually publish
rebar3 hex publish
# Publish documentation separately
rebar3 hex publish docs
# Revert a package (within 24h or 1 hour for new packages)
rebar3 hex revert 0.1.0
# Retire a version (soft deprecation)
rebar3 hex retire mylib 0.1.0 other "Please upgrade to 0.2.0"
```
### Version Retirement Reasons
```bash
# Available retirement reasons:
rebar3 hex retire mylib VERSION other "Custom message"
rebar3 hex retire mylib VERSION security "Security vulnerability found"
rebar3 hex retire mylib VERSION deprecated "Use new-package instead"
rebar3 hex retire mylib VERSION invalid "Invalid release"
rebar3 hex retire mylib VERSION renamed "Package renamed to new-name"
```
---
## Application Supervision
### Application Behavior
```erlang
-module(mylib_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
case mylib_sup:start_link() of
{ok, Pid} ->
{ok, Pid};
Error ->
Error
end.
stop(_State) ->
ok.
```
### Root Supervisor
```erlang
-module(mylib_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 => mylib_server,
start => {mylib_server, start_link, []},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [mylib_server]
},
#{
id => mylib_pool_sup,
start => {mylib_pool_sup, start_link, []},
restart => permanent,
shutdown => infinity,
type => supervisor,
modules => [mylib_pool_sup]
}
],
{ok, {SupFlags, ChildSpecs}}.
```
### Worker Pool Supervisor
```erlang
-module(mylib_pool_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
PoolSize = application:get_env(mylib, pool_size, 10),
SupFlags = #{
strategy => one_for_one,
intensity => 10,
period => 60
},
ChildSpecs = [
#{
id => {mylib_worker, N},
start => {mylib_worker, start_link, [N]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [mylib_worker]
}
|| N <- lists:seq(1, PoolSize)
],
{ok, {SupFlags, ChildSpecs}}.
```
---
## Configuration Management
### Application Environment
```erlang
% src/mylib.app.src
{application, mylib,
[{env, [
{pool_size, 10},
{timeout, 5000},
{retry_count, 3},
{log_level, info}
]}
]}.
```
### Runtime Configuration
```erlang
-module(mylib_config).
-export([get/1, get/2, set/2]).
%% @doc Get configuration value
-spec get(Key :: atom()) -> term().
get(Key) ->
case application:get_env(mylib, Key) of
{ok, Value} -> Value;
undefined -> error({config_not_found, Key})
end.
%% @doc Get configuration value with default
-spec get(Key :: atom(), Default :: term()) -> term().
get(Key, Default) ->
application:get_env(mylib, Key, Default).
%% @doc Set configuration value at runtime
-spec set(Key :: atom(), Value :: term()) -> ok.
set(Key, Value) ->
application:set_env(mylib, Key, Value).
```
### Config Files (sys.config)
```erlang
% config/sys.config
[
{mylib, [
{pool_size, 20},
{timeout, 10000},
{log_level, debug}
]},
{sasl, [
{sasl_error_logger, {file, "log/sasl-error.log"}},
{errlog_type, error}
]},
{kernel, [
{logger_level, info}
]}
].
```
---
## Common Patterns
### Singleton Server
```erlang
-module(mylib_server).
-behaviour(gen_server).
%% API
-export([start_link/0, call/1, cast/1, get_state/0]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-define(SERVER, ?MODULE).
%%% API
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
call(Request) ->
gen_server:call(?SERVER, Request).
cast(Request) ->
gen_server:cast(?SERVER, Request).
get_state() ->
gen_server:call(?SERVER, get_state).
%%% Callbacks
init([]) ->
State = #{
started_at => erlang:system_time(second),
requests => 0
},
{ok, State}.
handle_call(get_state, _From, State) ->
{reply, State, State};
handle_call(Request, _From, State = #{requests := Count}) ->
Result = process_request(Request),
{reply, Result, State#{requests => Count + 1}}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
%%% Internal
process_request(Request) ->
{ok, Request}.
```
### Resource Pool Pattern
```erlang
-module(mylib_pool).
-export([checkout/0, checkin/1, with_resource/1]).
-define(POOL, mylib_resource_pool).
%% @doc Check out a resource from the pool
-spec checkout() -> {ok, pid()} | {error, no_resources}.
checkout() ->
case ets:lookup(?POOL, available) of
[{available, [Pid | Rest]}] ->
ets:insert(?POOL, {available, Rest}),
ets:insert(?POOL, {in_use, Pid}),
{ok, Pid};
_ ->
{error, no_resources}
end.
%% @doc Return a resource to the pool
-spec checkin(pid()) -> ok.
checkin(Pid) ->
ets:delete_object(?POOL, {in_use, Pid}),
[{available, Available}] = ets:lookup(?POOL, available),
ets:insert(?POOL, {available, [Pid | Available]}),
ok.
%% @doc Execute function with a resource
-spec with_resource(fun((pid()) -> term())) -> {ok, term()} | {error, term()}.
with_resource(Fun) ->
case checkout() of
{ok, Resource} ->
try
Result = Fun(Resource),
{ok, Result}
after
checkin(Resource)
end;
Error ->
Error
end.
```
---
## Anti-Patterns
### 1. Exposing Internal Processes
```erlang
% Bad: Exposes internal PIDs
-spec get_worker() -> pid().
get_worker() ->
whereis(mylib_worker).
% Good: Provide functional API
-spec process(Input :: term()) -> {ok, term()} | {error, term()}.
process(Input) ->
mylib_worker:process(Input).
```
### 2. Breaking Module Contracts
```erlang
% Bad: Changing return type in new version
% v0.1.0
-spec parse(binary()) -> map().
% v0.2.0 - BREAKING
-spec parse(binary()) -> {ok, map()} | {error, term()}.
% Good: Add new function, deprecate old
% v0.2.0
-spec parse(binary()) -> map(). % Deprecated
-spec parse_safe(binary()) -> {ok, map()} | {error, term()}.
```
### 3. Ignoring Application Lifecycle
```erlang
% Bad: Starting processes in module
-module(mylib).
start_worker() ->
spawn(fun() -> worker_loop() end).
% Good: Use supervision tree
-module(mylib_sup).
init([]) ->
ChildSpecs = [worker_spec()],
{ok, {sup_flags(), ChildSpecs}}.
```
### 4. Missing Type Specs
```erlang
% Bad: No specs, dialyzer can't verify
process(Input) ->
transform(Input).
% Good: Full specifications
-spec process(Input :: binary()) -> {ok, term()} | {error, atom()}.
process(Input) when is_binary(Input) ->
transform(Input).
```
---
## References
- `meta-library-dev` - Foundational library patterns
- `lang-erlang-dev` - Core Erlang/OTP patterns
- [Rebar3 Documentation](https://rebar3.org/)
- [Hex Package Manager](https://hex.pm/)
- [EDoc User's Guide](https://www.erlang.org/doc/apps/edoc/chapter.html)
- [Dialyzer User's Guide](https://www.erlang.org/doc/man/dialyzer.html)
- [OTP Design Principles](https://www.erlang.org/doc/design_principles/des_princ.html)
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!