Search code examples
erlangerlang-otp

Erlang beginnings: moving a function from an escript into OTP


There is a simple implementation of the factorial function in an 'escript' in the Erlang docs. The factorial function is given as:

fac(0) -> 1;
fac(N) -> N * fac(N-1).

That's all fine, I can get this to work, no problem.

I would however like to know how I can implement this same, simple factorial function in an 'OTP way' using rebar3?

Just to be clear, my questions are:

  • Where does the code go?
  • How would I call it from the shell?
  • Could I also run it from the command line like I do via the escript example?

FYI, I have gotten started with rebar3. Here is where I am at:

rebar3 new app factorial

creates a few files but specifically the code is in 3 files in a src directory. I can see that a supervisor is being used, seems fine.

I can interact with this project from the shell:

$ rebar3 shell

1> application:which_applications().
[{factorial,"An OTP application","0.1.0"},
 {inets,"INETS  CXC 138 49","7.0.3"},
 {ssl,"Erlang/OTP SSL application","9.1.1"},
 {public_key,"Public key infrastructure","1.6.4"},
 {asn1,"The Erlang ASN1 compiler version 5.0.8","5.0.8"},
 {crypto,"CRYPTO","4.4"},
 {stdlib,"ERTS  CXC 138 10","3.7"},
 {kernel,"ERTS  CXC 138 10","6.2"}]
2> application:stop(factorial).
=INFO REPORT==== 21-Jan-2019::12:42:07.484244 ===
    application: factorial
    exited: stopped
    type: temporary
ok
3> application:start(factorial).
ok

Solution

  • Where does the code go?

    To 'call code in the OTP way', you can put it behind a gen_server.

    For this simple factorial function, I added a new file factorial.erl within the src directory which is pretty much a standard gen_server skeleton with my factorial function as one of the callbacks:

    % factorial.erl
    -module(factorial).
    -behaviour(gen_server).
    -export([start_link/0, stop/0, calc/1]).
    
    <boilerplate gen_server stuff here, like init, etc.>
    
    calc(N) ->
      {ok, Result} = gen_server:call(?SERVER, {calc, N}),
      {ok, Result}.
    
    handle_call({calc, N}, _From, State) ->
      Factorial = factorial(N),
      Reply = {ok, Factorial},
      {reply, Reply, State};
    
    factorial(0) ->
      1;
    factorial(N) ->
      N * factorial(N-1).
    

    Since my rebar3 new app factorial created a supervisor, I modified the supervisor's init so that it calls my factorial module:

    % factorial_sup.erl
    
    <skeleton supervisor stuff here>
    
    init([]) ->
      Server = {factorial, {factorial, start_link, []},
                permanent, 2000, worker, [factorial]},
      Children = [Server],
      RestartStrategy = {one_for_one, 0, 1},
      {ok, {RestartStrategy, Children}}.
    

    How do I call it from the shell?

    $ rebar3 shell
    <Enter>
    1> factorial:calc(5).
    {ok,120}
    

    Since this is running under a supervisor, we can still stop and restart it:

    2> application:stop(factorial).
    =INFO REPORT==== 22-Jan-2019::13:31:29.243520 ===
        application: factorial
        exited: stopped
        type: temporary
    ok
    3> factorial:calc(5).          
    ** exception exit: {noproc,{gen_server,call,[factorial,{calc,5}]}}
         in function  gen_server:call/2 (gen_server.erl, line 215)
         in call from factorial:calc/1 (/Users/robert/git/factorial/src/factorial.erl, line 32)
    4> application:start(factorial).
    ok
    5> factorial:calc(5).           
    {ok,120}
    

    How do I create an executable?

    Work in progress :-).