Erlang/OTP Forums

Author Message

<  Erlang questions mailing list  ~  Concept of Side-effect

Guest
Posted: Wed Sep 16, 2009 10:28 pm Reply with quote
Guest
Hi,

Can someone explain what does side-effect means in Erlang? What is
side-effect free function, and why we need to write side-effect free
function? A concrete example is preferred.

Thanks,

kaiduan

________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
Guest
Posted: Thu Sep 17, 2009 6:30 am Reply with quote
Guest
It means the same thing in Erlang as it does anywhere else. A function has a
side effect, if it changes some state. Doing something with files, sockets,
databases, processes etc. are side effects.
A side effect free function is dependent only on its input values. When it
has finished, it produces an output from those input values and nothing else
(list_to_binary/1 for instance).
You should strive to have as many side effect free functions as possible.
They are easy to test, easy to understand and easy to work with.
Functions that change state is where your bugs will generally be. The more
you change state, the more complex your program becomes. If you change state
all over the place, you are creating something very difficult to understand
and debug.


Sergej

On Thu, Sep 17, 2009 at 12:26 AM, Kaiduan Xie <kaiduanx@gmail.com> wrote:

> Hi,
>
> Can someone explain what does side-effect means in Erlang? What is
> side-effect free function, and why we need to write side-effect free
> function? A concrete example is preferred.
>
> Thanks,
>
> kaiduan
>
> ________________________________________________________________
> erlang-questions mailing list. See http://www.erlang.org/faq.html
> erlang-questions (at) erlang.org
>
>


Post received from mailinglist
Guest
Posted: Thu Sep 17, 2009 3:09 pm Reply with quote
Guest
On Sep 16, 3:26
Guest
Posted: Fri Sep 18, 2009 1:18 am Reply with quote
Guest
Thanks all for the reply. Can someone provide an example in code? For
example, a side-effect free function, and its counterpart function
with side-effect.

Thanks,

kaiduan

On Thu, Sep 17, 2009 at 11:07 AM, Gene Tani <gene.tani@gmail.com> wrote:
>
>
> On Sep 16, 3:26
kagato
Posted: Fri Sep 18, 2009 1:36 am Reply with quote
User Joined: 30 Dec 2007 Posts: 85
I'll do better. Here's an example that actually is necessary.

Assume you have two processes. One updates a record in Mnesia, the
other receives an acknowledgement that it did it.

Mnesia can replay a transaction in times of extreme contention.
Here's a buggy version of a function that charges some account. Look
at the fun() inside. It has the side-effect that it sends a message
to another process.

> charge_account(Requestor,UserId,Amount) ->
> {atomic,ok} = mnesia:transaction(
> fun() ->
> [ Current ] = mnesia:read(account,UserId),
> OldAmount = Current#account.amount,
> New = Current#account{ amount = OldAmount - Amount },
> ok = mnesia:write(New),
> Requestor ! {updated,New},
> ok
> end
> ).

This function will potentially send the message to Requestor multiple
times if the transaction has to be retried.

If you want to do this correctly, you can simply modify the function
like this:

> charge_account(Requestor,UserId,Amount) ->
> {atomic,Result} = mnesia:transaction(
> fun() ->
> [ Current ] = mnesia:read(account,UserId),
> OldAmount = Current#account.amount,
> New = Current#account{ amount = OldAmount - Amount },
> ok = mnesia:write(New),
> New
> end,
> Requestor ! {updated,Result},
> ).

The inner fun() has no side-effects and can now be safely retried by
Mnesia.

This is only one instance where side-effects matter. The important
thing to remember is that state data in Erlang takes the form of
recursion, processes and messages. If you don't create recurse with a
modified value, create a process, or send a message, then no state is
changed. Even when you recurse, your state only affects a single
process. This property makes it very easy to create very stable
programs, because you can contain the places that state can get
corrupted or lost.

Hopefully this helps.

On Sep 17, 2009, at 6:17 PM, Kaiduan Xie wrote:

> Thanks all for the reply. Can someone provide an example in code? For
> example, a side-effect free function, and its counterpart function
> with side-effect.
>
> Thanks,
>
> kaiduan
>
> On Thu, Sep 17, 2009 at 11:07 AM, Gene Tani <gene.tani@gmail.com>
> wrote:
>>
>>
>> On Sep 16, 3:26 pm, Kaiduan Xie <kaidu...@gmail.com> wrote:
>>> Hi,
>>>
>>> Can someone explain what does side-effect means in Erlang? What is
>>> side-effect free function, and why we need to write side-effect free
>>> function? A concrete example is preferred.
>>>
>>> Thanks,
>>>
>>> kaiduan
>>>
>>> ________________________________________________________________
>>> erlang-questions mailing list. Seehttp://www.erlang.org/faq.html
>>> erlang-questions (at) erlang.org
>>
>> http://blog.tornkvist.org/blog.yaws?id=1239107937892262
>>
>> http://www.cs.chalmers.se/Cs/Grundutb/Kurser/ppxt/HT2007/general/languages/armstrong-erlang_history.pdf
>> (PDF page 14)
>>
>> and a FP vs. OO debate:
>>
>> http://news.ycombinator.com/item?id=493963
>>
>> ________________________________________________________________
>> erlang-questions mailing list. See http://www.erlang.org/faq.html
>> erlang-questions (at) erlang.org
>>
>>
>
> ________________________________________________________________
> erlang-questions mailing list. See http://www.erlang.org/faq.html
> erlang-questions (at) erlang.org
>



--
Jayson Vantuyl
kagato@souja.net





Post received from mailinglist
View user's profile Send private message
kagato
Posted: Fri Sep 18, 2009 1:49 am Reply with quote
User Joined: 30 Dec 2007 Posts: 85
A small addendum.

Note that calling unsafe functions can create side-effects. Things
like gen_server:call, for example, send messages. So you need to be
careful with library functions.

Thinking a bit, other times that you can modify state:

* Changing the process dictionary (most people don't really do this)
* ETS tables (messages in disguise)
* DETS tables (messages in disguise)
* Network Connections (messages in disguise)
* digraphs (messages in disguise)

On Sep 17, 2009, at 6:35 PM, Jayson Vantuyl wrote:

> I'll do better. Here's an example that actually is necessary.
>
> Assume you have two processes. One updates a record in Mnesia, the
> other receives an acknowledgement that it did it.
>
> Mnesia can replay a transaction in times of extreme contention.
> Here's a buggy version of a function that charges some account.
> Look at the fun() inside. It has the side-effect that it sends a
> message to another process.
>
>> charge_account(Requestor,UserId,Amount) ->
>> {atomic,ok} = mnesia:transaction(
>> fun() ->
>> [ Current ] = mnesia:read(account,UserId),
>> OldAmount = Current#account.amount,
>> New = Current#account{ amount = OldAmount - Amount },
>> ok = mnesia:write(New),
>> Requestor ! {updated,New},
>> ok
>> end
>> ).
>
> This function will potentially send the message to Requestor
> multiple times if the transaction has to be retried.
>
> If you want to do this correctly, you can simply modify the function
> like this:
>
>> charge_account(Requestor,UserId,Amount) ->
>> {atomic,Result} = mnesia:transaction(
>> fun() ->
>> [ Current ] = mnesia:read(account,UserId),
>> OldAmount = Current#account.amount,
>> New = Current#account{ amount = OldAmount - Amount },
>> ok = mnesia:write(New),
>> New
>> end,
>> Requestor ! {updated,Result},
>> ).
>
> The inner fun() has no side-effects and can now be safely retried by
> Mnesia.
>
> This is only one instance where side-effects matter. The important
> thing to remember is that state data in Erlang takes the form of
> recursion, processes and messages. If you don't create recurse with
> a modified value, create a process, or send a message, then no state
> is changed. Even when you recurse, your state only affects a single
> process. This property makes it very easy to create very stable
> programs, because you can contain the places that state can get
> corrupted or lost.
>
> Hopefully this helps.
>
> On Sep 17, 2009, at 6:17 PM, Kaiduan Xie wrote:
>
>> Thanks all for the reply. Can someone provide an example in code? For
>> example, a side-effect free function, and its counterpart function
>> with side-effect.
>>
>> Thanks,
>>
>> kaiduan
>>
>> On Thu, Sep 17, 2009 at 11:07 AM, Gene Tani <gene.tani@gmail.com>
>> wrote:
>>>
>>>
>>> On Sep 16, 3:26 pm, Kaiduan Xie <kaidu...@gmail.com> wrote:
>>>> Hi,
>>>>
>>>> Can someone explain what does side-effect means in Erlang? What is
>>>> side-effect free function, and why we need to write side-effect
>>>> free
>>>> function? A concrete example is preferred.
>>>>
>>>> Thanks,
>>>>
>>>> kaiduan
>>>>
>>>> ________________________________________________________________
>>>> erlang-questions mailing list. Seehttp://www.erlang.org/faq.html
>>>> erlang-questions (at) erlang.org
>>>
>>> http://blog.tornkvist.org/blog.yaws?id=1239107937892262
>>>
>>> http://www.cs.chalmers.se/Cs/Grundutb/Kurser/ppxt/HT2007/general/languages/armstrong-erlang_history.pdf
>>> (PDF page 14)
>>>
>>> and a FP vs. OO debate:
>>>
>>> http://news.ycombinator.com/item?id=493963
>>>
>>> ________________________________________________________________
>>> erlang-questions mailing list. See http://www.erlang.org/faq.html
>>> erlang-questions (at) erlang.org
>>>
>>>
>>
>> ________________________________________________________________
>> erlang-questions mailing list. See http://www.erlang.org/faq.html
>> erlang-questions (at) erlang.org
>>
>
>
>
> --
> Jayson Vantuyl
> kagato@souja.net
>
>
>



--
Jayson Vantuyl
kagato@souja.net




________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
View user's profile Send private message
Guest
Posted: Fri Sep 18, 2009 2:22 am Reply with quote
Guest
Thanks a lot for the great examples. But I can not understand the following one,

add(X,Y) ->
X+Y.

add_effects(X,Y) ->
io:format("~p~n",[X+Y]).

What side-effect add_effects generates? Why we say io:format()
generates side-effect?

kaiduan

On Thu, Sep 17, 2009 at 9:47 PM, Jayson Vantuyl <kagato@souja.net> wrote:
> A small addendum.
>
> Note that calling unsafe functions can create side-effects.
Guest
Posted: Fri Sep 18, 2009 2:35 am Reply with quote
Guest
Thanks Benjamin, but sometimes printing out to the console is really
what you want. Maybe this is not a good example Smile

kaiduan

On Thu, Sep 17, 2009 at 10:18 PM, Benjamin Tolputt
<btolputt@bigpond.net.au> wrote:
> Kaiduan Xie wrote:
>> Thanks a lot for the great examples. But I can not understand the following one,
>>
>> add(X,Y) ->
>>
mtruog
Posted: Fri Sep 18, 2009 3:16 am Reply with quote
User Joined: 07 Feb 2009 Posts: 50 Location: San Francisco, CA
It is the idea that it is modifying state outside the function. So
printing to the console is a side-effect, it is externalized state.

Kaiduan Xie wrote:
> Thanks Benjamin, but sometimes printing out to the console is really
> what you want. Maybe this is not a good example Smile
>
> kaiduan
>
> On Thu, Sep 17, 2009 at 10:18 PM, Benjamin Tolputt
> <btolputt@bigpond.net.au> wrote:
>
>> Kaiduan Xie wrote:
>>
>>> Thanks a lot for the great examples. But I can not understand the following one,
>>>
>>> add(X,Y) ->
>>> X+Y.
>>>
>>> add_effects(X,Y) ->
>>> io:format("~p~n",[X+Y]).
>>>
>>> What side-effect add_effects generates? Why we say io:format()
>>> generates side-effect?
>>>
>>>
>> The side-effect is that there is something printed to the console (or
>> stdout). If there were another function (say 'print_screen') that could
>> look at the screen, it is possible/likely a call to the add_effects
>> function would change the output of the function.
>>
>> --
>> Regards,
>>
>> Benjamin Tolputt
>> Analyst Programmer
>>
>>
>>
>
> ________________________________________________________________
> erlang-questions mailing list. See http://www.erlang.org/faq.html
> erlang-questions (at) erlang.org
>
>
>


________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
View user's profile Send private message AIM Address Yahoo Messenger
jeffm
Posted: Fri Sep 18, 2009 3:38 am Reply with quote
User Joined: 29 Sep 2008 Posts: 43
Kaiduan Xie wrote:
> Thanks Benjamin, but sometimes printing out to the console is really
> what you want. Maybe this is not a good example Smile
>

Even when you do want the program to print to the screen it is a
side-effect.

Lets try breaking it down:


>>> add(X,Y) ->
>>> X+Y.
>>>

call add/2 with X and Y
calculate X+Y
return result
result now available in calling function

>>> add_effects(X,Y) ->
>>> io:format("~p~n",[X+Y]).
>>>
>>>

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

>>> What side-effect add_effects generates? Why we say io:format()
>>> generates side-effect?
>>>
>>>
>>>
note how in the second example the calling function doesn't know
anything about the effects of the io:format/2 function call. It is an
effect outside the, or aside from, main excution path and hence a
side-effect.

Jeff.


________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
View user's profile Send private message
Guest
Posted: Fri Sep 18, 2009 6:26 am Reply with quote
Guest
Kaiduan Xie wrote:
> Thanks a lot for the great examples. But I can not understand the following one,
>
> add(X,Y) ->
> X+Y.
>
> add_effects(X,Y) ->
> io:format("~p~n",[X+Y]).
>
> What side-effect add_effects generates? Why we say io:format()
> generates side-effect?
>

The side-effect is that there is something printed to the console (or
stdout). If there were another function (say 'print_screen') that could
look at the screen, it is possible/likely a call to the add_effects
function would change the output of the function.

--
Regards,

Benjamin Tolputt
Analyst Programmer


________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
Guest
Posted: Fri Sep 18, 2009 7:36 am Reply with quote
Guest
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

________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
Guest
Posted: Fri Sep 18, 2009 8:21 am Reply with quote
Guest
On 18 Sep 2009, at 03:35 , Jayson Vantuyl wrote:
>> charge_account(Requestor,UserId,Amount) ->
>> {atomic,Result} = mnesia:transaction(
>> fun() ->
>> [ Current ] = mnesia:read(account,UserId),
>> OldAmount = Current#account.amount,
>> New = Current#account{ amount = OldAmount - Amount },
>> ok = mnesia:write(New),
>> New
>> end,
>> Requestor ! {updated,Result},
>> ).
>
> The inner fun() has no side-effects

Not so. The inner fun() executes within a transactional context (that
of mnesia) and only uses functions relative to that context, which
means the context manager can handle errors & retries gracefully.

However, mnesia:read and mnesia:write are still side-effecting
functions, not pure ones: they read and alter external state which
means the function isn't necessarily repeatable (if you call the fun()
twice in a row, the record might have been deleted in-between,
yielding different outcomes).

A pure version of it would be the following:

fun(Db) ->
[ Current ] = mnesia:read(Db, account, UserId),
OldAmmount = Current#account.amount,
New = Current#account{amount = OldAmount - Amount },
NewDb = mnesia:write(Db, New),
NewDb
end

here, a database is provided as argument to the function and instead
of mutating that database we create a new one.

This means if we repeatedly call this function and always provide it
the same Db input, it'll always return the same NewDb. It doesn't rely
on any external state, managed or not. It's pure.


________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
Guest
Posted: Fri Sep 18, 2009 8:25 am Reply with quote
Guest
On 18 Sep 2009, at 04:34 , Kaiduan Xie wrote:
> Thanks Benjamin, but sometimes printing out to the console is really
> what you want. Maybe this is not a good example Smile
>

The qualification of a side-effect as side-effect doesn't depend on
"what you want", sometimes you want side-effects (indeed, programming
would be quite uninteresting if we were only allowed pure constructs).

A side-effect is simply a contact with a state, either reading from or
writing to a state holder. In the case of your io:format, you're
writing to the console and altering its state (by adding a new line
with your printed string each time).

________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist
Guest
Posted: Fri Sep 18, 2009 8:31 am Reply with quote
Guest
Kaiduan Xie wrote:
> Benjamin Tolputt wrote:
>
>> Kaiduan Xie wrote:
>>
>>> Thanks a lot for the great examples. But I can not understand the following one,
>>>
>>> add(X,Y) ->
>>> X+Y.
>>>
>>> add_effects(X,Y) ->
>>> io:format("~p~n",[X+Y]).
>>>
>>> What side-effect add_effects generates? Why we say io:format()
>>> generates side-effect?
>>>
>> The side-effect is that there is something printed to the console (or
>> stdout). If there were another function (say 'print_screen') that could
>> look at the screen, it is possible/likely a call to the add_effects
>> function would change the output of the function.
>>
> Thanks Benjamin, but sometimes printing out to the console is really
> what you want. Maybe this is not a good example Smile
>

Oh there is no debating that you may want that behaviour just, that in
having the side-effect of printing to the screen, you then need to be
aware of other functions that use that state (i.e. the example
'print_screen').

Side-effects are not necessarily bad things - they just add to the
things you need to keep a track of. The less side-effects there are, the
more "local" you can limit your thoughts when coding new functionality.
Whilst I use Erlang for the things I can, I do a majority of my coding
in C/C++. A majority of the times I have found bugs, it has been
state-changing functionality (i.e. side-effects) in code I was unaware
of. Erlang makes it harder to create these side-effects by limiting the
ways in which it can be done Smile

P.S. Hope you don't mind my re-arrangement of your email - the "reply at
top" made it harder to follow the flow of the email.

--
Regards,

Benjamin Tolputt
Analyst Programmer


________________________________________________________________
erlang-questions mailing list. See http://www.erlang.org/faq.html
erlang-questions (at) erlang.org

Post received from mailinglist

Display posts from previous:  

All times are GMT
Page 1 of 2
Goto page 1, 2  Next
This forum is locked: you cannot post, reply to, or edit topics.

Jump to:  

You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You cannot download files in this forum