[erlang-questions] Re: Concept of Side-effect

Ulf Wiger ulf.wiger@REDACTED
Fri Sep 18 09:34:56 CEST 2009


jm wrote:
> 
> call add/2 with X and Y
>   calculate X+Y
>   pass message to io to print to screen (** side effect **)
>   return result
> result now available in calling function

Actually, the return result is 'ok', which basically
only tells the caller that the function didn't crash.

The main difference then becomes:

- add(X, Y) returns "This is the result of X+Y"
- add_effects(X,Y) returns "ok, I've done something"

In the case of add_effects/2, you have to go elsewhere
to find out exactly /what/ was done.

This is a fairly common pattern when you look at code
written by Erlang newbies. Some of my early sins where
cast in stone in the AXD 301 code base and its successors.
One was the initialization framework for creating mnesia
tables, which I designed 11 years ago.

I came up with a callback module, <App>DataInit.erl, which
was found automatically by the install program. It exported
functions to

1. create tables
2. add data
3. optionally do something afterwards

These phases were called across all applications, so first,
all tables were created, then they were populated, and then
further work could be done on the data.

This worked very well, except for one thing: the API semantics
were such that functions either returned 'ok' (in which case
a smiley was output for that function) or {error, Reason}
(a sad face printed, together with the error). While this made
it very easy to see on the screen that everything went well,
what the functions did - if they did anything at all - was
completely opaque. When we wanted to describe the full data
model, it became very clear that this wasn't a very good idea.

The problem was that the API reflected an imperative mindset:

  "Go and do something" -> "ok, I've done something"

It is a bit more difficult to craft an API that is more
functional in nature:

  "What is your data model" -> "This is my data model"

but in the long run, it is much more powerful.
It would not only have allowed for easy extraction of the
data model (perhaps even as part of static analysis), but
also for coping with new situations.

Much later, we discussed the option of upgrading the system
by taking some nodes off-line, converting them to the
newer version, bringing them on-line, synching, and then
basically bootstrapping the other half to the new version.
Converting the database off-line could have been automated
as a well-isolated project if the data initialization modules
had been more declarative in nature, but with the imperative
model, a new callback - "Go and upgrade your data off-line" ->
"ok, I've done that" (at least one such callback, perhaps more)
would be needed in all applications, which made it a project
that affected every single design team.

There are other examples that are similar in nature.
I've complained now and then about the way the supervision
hierarchy is built in OTP.

- First, you name an application callback module in the .app
   file.
- The application controller calls Mod:start(Type, Args) in
   order to start the application. It is supposed to return
   {ok, Pid} ("ok, something happened. Here's your handle.")
- What the start function normally does is call
   supervisor:start_link(...) with another callback module.
   This function returns {ok, Pid} (again, imperative)
- The init function in the supervisor callback module
   is /usually/ declarative by nature. It is supposed to
   return a specification, which leads to the starting of
   workers.

This part, we actually /did/ get right in the AXD 301, IMHO.
I felt that it was tedious to chase this down every time,
and guessed that >90% of all applications could get away with
just specifying the supervisor declaration as an environment
variable in the .app file, and call a generic start function.
This worked like a charm, and made it much easier to see
which processes were actually being started, what supervision
strategies where being used, and likely removed a lot of
unnecessarily imperative code (since it was easier to just
declare the structure statically).

BR,
Ulf W
-- 
Ulf Wiger
CTO, Erlang Training & Consulting Ltd
http://www.erlang-consulting.com


More information about the erlang-questions mailing list