If I understood your question, here is an example for an approach, it helps a lot for me. This approach helps to separate the interface and implementation.
"Interface".
-module(contract).
-export([
new/2,
do_something/2
]).
%% Behavioural callbacks definition. Each of "derived" modules should implement it.
-callback new(Arg :: any()) -> {ok, ImplState :: any()} | {error, Reason :: atom()}.
-callback do_something( Arg :: any(), ImplState :: any() ) -> {ok, ReturnVal :: any(), NewImplState :: any()} | {error, Reason :: atom()}.
%% Opaque state to hold implementation details
-record(
contract_impl, {
impl_module :: module(),
impl_state :: any()
}
).
%% Interface for creation "polymorphic" instance, like base-class constructor.
new(ImplModule, ImplInitArgs) ->
case ImplModule:new(ImplInitArgs) of
{ok, State} ->
{ok,
impl_module = ImplModule,
impl_state = State
}
};
{error, Reason} ->
{error, Reason}
end.
%% Interface function, like an abstract method in OOP.
do_something(
Arg,
impl_module = ImplModule,
impl_state = ImplState
} = ContractImpl
) ->
case ImplModule:do_something(Arg, ImplState) of
{ok, ReturnVal, NewState} ->
{ok, ReturnVal, ContractImpl
{error, Reason} -> {error, Reason}
end.
An example implementation (for example, a derived class).
-module(foo).
-behaviour(contract).
-export([
new/1,
do_something/2
]).
-record(
foo_state, {
repeat_count
}
).
new(Options) ->
{ok,
repeat_count = proplists:get_value(repeat_count, Options)
}
}.
do_something(Arg,
Result = [ io_lib:format("Foo ~p", [Arg]) || _Index <- lists:seq(1, RepeatCount) ],
{ok, Result, State}.
Now you can do the following:
usage_example() ->
{ok, State} = contract:new(foo, [{repeat_count, 15}]),
{ok, Result, NewState} = contract:do_something("bar", State),
ok.
Hope this helps.