user3169252
user3169252

Reputation: 1189

Erlang polymorphism: multiple implementations of the same contract?

What's correct Erlang way to have separated implementations from the contract and how to switch between them?

Upvotes: 7

Views: 1223

Answers (5)

Hynek -Pichi- Vychodil
Hynek -Pichi- Vychodil

Reputation: 26121

Nice example of polymorphism is qlc module and structure table. See various M:table/1,2 implementations in ets, dets, mnesia and so. Try ets:table(ets:new(foo, [set])). in shell for example and look into qlc documentation and examples.

Upvotes: 2

I GIVE CRAP ANSWERS
I GIVE CRAP ANSWERS

Reputation: 18879

While others mention the behaviour feature, it is merely a small helper to make sure you implement all functions in a module for a callback structure. If you have two implementations, a and b, and both implements the same functions, you can just statically substitute a for b in a calling module. For static configuration where you have a better implementation, this is preferable.

If the question is of a more dynamic nature, you can just do

 Mod = a,
 Mod:f(Args).

And then in code set Mod appropriately. This lets you dynamically control what module to call while the program is running. It is not entirely clear which of the two you want.

Upvotes: 4

Viacheslav Kovalev
Viacheslav Kovalev

Reputation: 1745

If I realized your question, here is example for approach, that pretty much works for me. This approach helps to separate interface and implementation.

"Interface" module.

-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,
        #contract_impl {
          impl_module = ImplModule,
          impl_state = State
        }
      };
    {error, Reason} ->
      {error, Reason}
  end.

%% Interface function, like an abstract method in OOP.
do_something(
  Arg,
  #contract_impl {
    impl_module = ImplModule,
    impl_state = ImplState
  } = ContractImpl
) ->
  case ImplModule:do_something(Arg, ImplState) of
    {ok, ReturnVal, NewState} ->
      {ok, ReturnVal, ContractImpl#contract_impl{ impl_state = NewState }};
    {error, Reason} -> {error, Reason}
  end.

Some implementation example (like derived class).

-module(foo).

-behaviour(contract).

-export([
  new/1,
  do_something/2
]).

-record(
  foo_state, {
    repeat_count
  }
).

new(Options) ->
  {ok,
    #foo_state{
      repeat_count = proplists:get_value(repeat_count, Options)
    }
  }.

do_something(Arg, #foo_state{ repeat_count = RepeatCount } = State) ->
  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.

I hope this helps.

Upvotes: 0

mkorszun
mkorszun

Reputation: 4571

Maybe have a look on behaviours concept. At least for me there is small similarity to OOP in terms of having interface definition and multiple implementation modules.

Upvotes: 1

fenollp
fenollp

Reputation: 2496

Since Erlang is dynamically typed, function guards (the when … -> bits) are the way to express polymorphism.

E.g:

len (T) when is_tuple(T) -> size(T);
len (L) when is_list(L) -> length(L).

Upvotes: 1

Related Questions