Erlang (programming language)/Tutorials/otp design: Difference between revisions

From Citizendium
Jump to navigation Jump to search
imported>Eric Evers
No edit summary
imported>Tom Morris
 
(3 intermediate revisions by one other user not shown)
Line 1: Line 1:
Example program for the OTP behaviour, gen_server.
===Fail early and often===


Here is an example program using gen_server to create a simple text game.
Fail early and often is a strange and unusual programming method used in OTP.
Processes are programed to a specification about how to handle good input. If
bad input is seen by a worker process we let the process crash.
The supervisor restarts the worker process. The effect is that the bad input is ignored.
By only programming to good inputs the program is kept short aiding
readability and maintainability.


<code>
Fail early and often should work well with any type of data flow situation
when a program is processing a flow of messages.
Logically, security and stability is increased by limiting
the propagation of errors to other processes from
the location of the error.


  -module(rock).
===Debugging OTP===
  -author("Eric Evers").
 
  -behaviour(gen_server).
Messages are fairly easy to track. By watching messages
  % ---------------------------------------------------------------
move between process, debugging can be simplified as compared with
  % Example of a gen_server
tracing low level state changes, which do not
  %  Purpose: a tutorial on gen_server
happen in processes written in a strict functional language.
  %  Program: a program that plays "rock, paper, scissors"
The only state changes that happen are in databases
  %    in spanish and english
if we use strict functional methods.
  %  Topics covered:
 
  %    * starting a gen_server
===Isolate the pure from the impure===
  %    * stoping a gen_server
 
  %    * using a gen_server to play a simple game
Write as much of the program as possible as a strictly
  %    * make a call within a call
functional program. Isolate the non-functional activities
  %    * use calls and casts
from the functional activities. This keeps the parts of
  %
the program with pesky state-changes as small as possible.
  % For more information see: 
 
  %
Note: The gray zone
  % References:
 
  %  Author:  Ericsson AB
Other exceptions to strict functional programming include
  %  Document: Erlang doumentation
the use of random number generators, and the sending and receiving
  %  Section:  OTP Design Principles
of messages. These two exceptions are not so difficult
  %  Page:    Gen_Server Behaviour 
to handle and are technically impure
  % ----------------------------------------------------------------
but often unavoidable and hence allowed in 'pure' code.
  %
  % - for internal use
  -export([init/1, handle_call/3, handle_cast/2]).  
  -export([handle_info/2, terminate/2, code_change/3]).
 
  % - quick halt (note: casts are Asynchronous)
  -export([stop/0]).
 
  % - for external use (note: calls are Synchronous)
  -export([start/0, translate/1, juego/1, list/0, play/1]).
 
  % @start the server
  start() ->            gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  list()  ->            gen_server:call(?MODULE, {list}).
  translate(Word) ->    gen_server:call(?MODULE, {translate, Word}).
  %
  % use juego to play the game using spanish words
  juego(Word) ->        gen_server:call(?MODULE, {juego, Word}).
 
  % use play to play the game using english words
  play(Word) ->        gen_server:call(?MODULE, {play, Word}).
  %
  % init
  init([]) -> Dictionary = dict:from_list(
    [{rock,roca},{paper,papel},{scissors,tijeras}]),
    {ok, Dictionary}.
  %
  % handle_call
  handle_call({list}, _From, Dictionary) ->
    Response = dict:to_list(Dictionary),
    {reply, Response, Dictionary};
  %
  % Here we make a call within a call
  handle_call({play, Word}, _From, Dictionary) ->
    Status = dict:is_key(Word, Dictionary),
    if
        Status == true ->
            SWord = dict:fetch(Word, Dictionary),
            {reply, Response, _} =
                handle_call({juego, SWord}, _From, Dictionary);
        true ->
            Response = your_word_is_not_valid
    end,
    {reply, Response, Dictionary};
  % 
  handle_call({juego, Word}, _From, Dictionary) ->
    List = dict:to_list(Dictionary),
    {_English, Spanish} = lists:unzip(List),
    Indexes = lists:seq(1, length(Spanish)),
    Enumerated = lists:zip(Spanish, Indexes),
    Enumerated_dictionary = dict:from_list(Enumerated),
    {_H,_M,S} = time(),
    N = (S rem length(Spanish)) + 1,
    Pick = lists:nth(N, Spanish),
    io:format("you play: ~w \n",[Word]),
    P = dict:fetch(Word, Enumerated_dictionary),
    Delta = ((N+3)-P) rem 3,
    Response = case Delta of
        0 -> {'I',picked,Pick,its_a,draw};
        1 -> {'I',picked,Pick,you,loose};
        2 -> {'I',picked,Pick,you,win};
        true -> {unknown, result}
    end,
    {reply, Response, Dictionary};
  %   
  handle_call({translate, Word}, _From, Dictionary) ->
    Response = case dict:is_key(Word, Dictionary) of
        true ->
            dict:fetch(Word, Dictionary);
        false ->
            {word_not_known, Word}
    end,
    {reply, Response, Dictionary};
  %
  handle_call(_Message, _From, Dictionary) -> {reply, error, Dictionary}.
  % -  
  stop() ->
    gen_server:cast(?MODULE, stop).
  %
  terminate(normal, _State) ->
    ok.
  %
  handle_cast(stop, State) ->
    {stop, normal, State};
  % 
  % - lets keep the compiler quiet with all the call-backs
  handle_cast(_Message, Dictionary) -> {noreply, Dictionary}.
  handle_info(_Message, Dictionary) -> {noreply, Dictionary}.
  code_change(_OldVersion, Dictionary, _Extra) -> {ok, Dictionary}.
 
  % - sample output -----------------------------
  % c(rock).
  %  <compile is done>
  % rock:start().
  %  <server started>
  %
  % 67> rock:juego(tijeras).
  % you play: tijeras
  % {'I',picked,roca,you,loose}
  %
  % 68> rock:juego(tijeras).
  % you play: tijeras
  % {'I',picked,papel,you,win}
  %
  % 69> rock:juego(tijeras).
  % you play: tijeras 
  % {'I',picked,tijeras,its_a,draw}
  % ----------------------------------------------------
</code>

Latest revision as of 06:07, 8 August 2009

Fail early and often

Fail early and often is a strange and unusual programming method used in OTP. Processes are programed to a specification about how to handle good input. If bad input is seen by a worker process we let the process crash. The supervisor restarts the worker process. The effect is that the bad input is ignored. By only programming to good inputs the program is kept short aiding readability and maintainability.

Fail early and often should work well with any type of data flow situation when a program is processing a flow of messages. Logically, security and stability is increased by limiting the propagation of errors to other processes from the location of the error.

Debugging OTP

Messages are fairly easy to track. By watching messages move between process, debugging can be simplified as compared with tracing low level state changes, which do not happen in processes written in a strict functional language. The only state changes that happen are in databases if we use strict functional methods.

Isolate the pure from the impure

Write as much of the program as possible as a strictly functional program. Isolate the non-functional activities from the functional activities. This keeps the parts of the program with pesky state-changes as small as possible.

Note: The gray zone

Other exceptions to strict functional programming include the use of random number generators, and the sending and receiving of messages. These two exceptions are not so difficult to handle and are technically impure but often unavoidable and hence allowed in 'pure' code.