Showing posts with label C++. Show all posts
Showing posts with label C++. Show all posts

Sunday, September 13, 2015

Erlang enum

Rationale

One day each Erlang developer with C/C++ background tries to find mechanism similar to enum. Enum it's light-weight datatype with all possible values enumerated. There are multiple options of Erlang implementation.

Macros

Many developers just define macros with some meaningful name and unique (within enumeration) value.
-define(AUTH_ERROR, 404).
-define(FORMAT_ERROR, 400).
-define(INTERNAL_ERROR, 500).

Usage of such enums requires including header file with definition. Typo in macro's name would lead to compilation error.
Advanced developers even define a macro, which checks whether a value belongs to enumeration type.
-define(IS_ERROR(V), (
    V =:= ?AUTH_ERROR orelse
    V =:= ?FORMAT_ERROR orelse
    V =:= ?INTERNAL_ERROR
)).
handle_error(E) when ?IS_ERROR(E) ->
  case E of
    ?AUTH_ERROR -> abort();
    ?FORMAT_ERROR -> abort();
    ?INTERNAL_ERROR -> retry()
  end.
The problem with this approach is that uniqueness of values is not checked by compiler. One can easily add a new error into enumeration with value assigned to a different element of enumeration.
-define(NOT_FOUND, 404).
Obviously it can break logic which relies on the enum.

Atoms

A set of atoms can also be used as enumeration. Atoms are unique identifiers within entire system. Header file is not required for atom-enum.
 handle_error(E) ->
  case E of
    auth_error -> abort();
    format_error -> abort();
    internal_error -> retry()
end.

The only problem is to check whether a given atom is an element of enumeration. This issue arises if somebody makes a typo error in atom name, for example auth_eror instead of auth_error. One can try to resolve it by defining macro function, which checks if an atom belongs to enum. The macro could be used as a guard experession.
-define(IS_ERROR(V), (
  V =:= auth_error orelse
  V =:= format_error orelse
  V =:= internal_error
)).
handle_error(E) when ?IS_ERROR(E) ->
  ...,
  ok.
...
handle_error(auth_eror).
It again requires including header file to use IS_ERROR macro in different modules, and the issue could be only found on run-time. Error in atom would not lead to compilation error.
In addition to that IDE would not be able to do automatic refactoring such as renaming, because it's not safe to rename an atom in entire project, and scope can not be narrowed down due to global nature of atoms. Even "find usages" might return irrelevant results.

Records

There is one more non-obvious way to implement enum in Erlang. Just define a record, where each field is an element of enum.
-record(http_errors,{
  auth_error,
  format_error,
  internal_error
}).

Now each element of enumeration can be referenced as a record field's position.
handle_error(E) ->
  case E of
    #http_errors.auth_error -> abort();
    #http_errors.format_error -> abort();
    #http_errors.internal_error -> retry()
  end.
handle_error(#http_errors.auth_error).

Obviously all enumeration elements are unique since record field's position is unique, and it's guaranteed by compiler (compare with macros approach).
Any typo in field name will cause a compilation error (compare with atoms approach).
Automatic refactoring becomes easy for IDE, for example, renaming record's field is trivial. "Find usages" function has a well defined scope.
Of course, usage of record requires including header file with it's definition, but that is a fair price for compile-time checks it provides.
Since record field's position is just an integer, it can be used for defining external API. If record definition string ("-record(http_errors,{") is on the first line of a file, and each field is on new line, each enumeration element would have an integer value, which equals to line number in source code, where it's defined.
  1. -record(http_errors,{
  2.   auth_error,        %%authentication error
  3.   format_error,      %%wrong json
  4.   internal_error     %%connection to db server is lost
  5. }).

For example, in http_errors enum auth_error element is on line 2, so it's integer values is 2. This value could be used as error code in RESTful API. It turns out that documentation is "generated" automatically.
Enum implementation using records is very close to C equivalent. It matches some verbose id with integer value. It automatically increments an interger value for each new element in enumeration. The only constraint is that integer value for the first element is always 2 and can not be changed.

Conclusion

To sum up, a comparison table is provided.

MacroAtomRecord
No include-+-
Uniqueness-++
Compile time membership check+-+
Automatic rename refactoring+-+
Force value+--

Erlang record approach seems to be the best option for it compile-time uniqueness guarantees and membership check.

Wednesday, March 18, 2015

Resource management idioms in Erlang

Resource management problem

Resource management is an important question in any programming language. It stays important even if runtime provides garbage collector, because there are other resource besides memory. Resource is something, what client has to initialise and later clean up, for example, socket, file descriptor or connection to RDBMS.

Language specific solutions

Resource management problem is solved in different languages differently.

C

Every C developer has to take care of resource deallocation after some usage manually. For example, each memory allocation with malloc must be followed with free. From the first sight such approach might look not that difficult, because there are no exceptions in C. But in fact it makes code much more complicated. Situation is slightly improved in frameworks such as Glib.

C++

C++ is successor of C, it introduces classes with constructors and destructors and guarantees calling of object's destructor when it goes out of scope. As a result Resource Acquisition Is Initialisation (RAII) idiom emerged together with smart pointers, which are an implementation of the idiom. In C++ exceptions are also introduced and RAII helps in exception safety. So resource could be represented as an object, where it's initialised in constructor and deallocated in destructor.

Java

Even though objects in Java have finalize method, which is called before destruction, it can not be safely used for resource deallocation as C++ destructor. It's because Java runtime uses garbage collector and the moment of clean up is not guaranteed.
Instead try-finally block can be used
Type res = new Type();
try
{
    res.use();
}
finally
{
    res.deallocate();
}
The same approach is used in other languages with garbage collector (C#, Python, etc.) sometimes with additional syntax sugar.

Erlang

In Erlang developer can use all there techniques of resource management listed above.

Manual management

I assume that everybody is aware of disadvantages of C-style resource management and tries to limit it's usage, I would not go into details.

RAII

Some of OTP behaviours (gen_serv, gen_fsm) have init and terminate callbacks, which could be used for resource allocation and deallocation correspondingly. terminate is called right before process termination and fits for most usages of resource deallocation. The only potential issue could be a strict synchronisation of clean up with another process, which requires additional message sending.
Famous examples of RAII idiom from OTP are ETS and gen_tcp. ETS table is cleaned up by default, if process, which created it, terminates. Socket opened in context of process is closed on process termination as well.

try-catch

Resource deallocation function is guaranteed to be called in after clause of try-catch block. For example, this is how poolboy:transaction function is implemented.
transaction(Pool, Fun, Timeout) ->
    Worker = poolboy:checkout(Pool, true, Timeout),
    try
        Fun(Worker)
    after
        ok = poolboy:checkin(Pool, Worker)
    end.

Erlang-specific recommendations

The rule of thumb for choosing between try-catch block and RAII(separate process) is quite simple. Try-catch is easier and shorter, but is not applicable if allocated resource should exist during processing of next message/callback in caller process. It also can not be used if you need to share a resource between multiple processes.
Do not try to save few lines of boilerplate code for a new gen_server/gen_fsm module for resource management, when it's needed. It will eventually pay off with cleaner code.

General recommendations

Try to avoid manual (C-style) resource management.
Do not handle multiple resources neither in single try-catch block, nor in single object, which implements RAII idiom, because it's difficult to implement correctly.