dragonfi
dragonfi

Reputation: 21

What is the Erlang idiom for platform specific modules with the same contract

I have an Erlang program that can be compiled for multiple platforms.

What I want is to separate code for the different platforms. What would be the most Erlang way to go about this?

I want to achieve something like this:

-module(platform_1).
-export([platform_init/0, platform_do_work/1, platform_stop/0]).
...

-module(platform_2).
-export([platform_init/0, platform_do_work/1, platform_stop/0]).
...

-module(main).
-export([start/0]).
main() ->
  platform::init(),
  platform::do_work("The work"),
  platform::stop().

(Obviously the above code does not work, it's missing the part I'm asking about.)

  1. I could name both modules platform and provide only one of them during compilation.
  2. I could use -ifdefs in a platform module to wrap the platform specific modules.
  3. I could use -behavior to specify a common contract.
  4. I could use a header file with -export macros to provide a common contract.

I'm sure there are other solutions out there. I just didn't find any idioms out there for this general use case.

Upvotes: 2

Views: 70

Answers (2)

Pascal
Pascal

Reputation: 14042

One simple way to achieve what you want is to store the module in a variable and use this variable to call the right module/function.

For example you could start your application with an extra parameter which select the right platform:

erl [your parameters] -extra "platform=platform_1"

Define your platform dependant modules as you want

-module(platform_1).
-export([platform_init/0, platform_do_work/1, platform_stop/0]).
...

and

-module(platform_2).
-export([platform_init/0, platform_do_work/1, platform_stop/0]).
...

and retrieve in the main function the Platform parameter

-module(main).
-export([start/0]).
main() ->
  Args = init:get_plain_arguments().
  [Platform] =
    [list_to_atom(lists:nthtail(9,Y))
    || Y <- Args, string:prefix(Y,"platform=") =/= nomatch].
  Platform:init(),
  Platform:do_work("The work"),
  Platform:stop().

I am not convinced at all that is the best way, mainly because you need to know, when you use a module if it is platform dependent or not (and the code to get the platform is not clean).

I think it would be better if the modules themselves were responsible for the platform choice. I will try to complete this answer as soon as I have some spare time.

Upvotes: 1

Brujo Benavides
Brujo Benavides

Reputation: 1958

My first instinct here would be to define a new behavior, like:

-module platform.

-type state() :: term().
-type work() :: {some, work} | ….

-callback init() -> {ok, state()}.
-callback do_work(work(), state()) -> {ok, state()}.
-callback stop(state()) -> ok.

-export([run/1]).

-spec run(module()) -> ok.
run(Module) ->
  {ok, State0} = Module:init(),
  {ok, State1} = Module:do_work({some, work}, State0),
  ok = Module:stop(State1).

Then, your modules can implement the behavior (I would probably place them in a folder like src/platforms and they'll look something like…

-module first_platform.
-behavior platform.
-export [init/0, do_work/2, stop/1].

-spec init() -> {ok, platform:state()}.
init() –>
  …,
  {ok, State}.
…

And your main module can have a macro or an environment variable or something where it can retrieve the platform to use, and look something like…

-module main.
-export [start/0].

-spec start() -> ok.
start() ->
  Platform =
    application:get_env(your_app, platform, first_platform),
  platform:run(Platform).

or even…

-module main.
-export [start/0].

-spec start() -> ok.
start() ->
  platform:run().
  

…and you do the figuring out of which platform to use within platform itself.

Upvotes: 3

Related Questions