[erlang-questions] Clueless am I

zxq9 zxq9@REDACTED
Tue Apr 26 09:16:14 CEST 2016


On 2016年4月26日 火曜日 17:25:57 Richard A. O'Keefe wrote:
> 
> On 26/04/16 7:15 AM, Donald Steven wrote:
> > Dear friends,
> >
> > Please forgive me for being clueless.  (I'm sure it's because I come 
> > to Erlang steeped in C.)
> > %% THIS DOES NOT WORK
> >
> > -module(arraytest).
> > -export([main/0]).
> >
> > main() ->
> >
> >     A1 = array:set(0, nextevent(), array:new(20)),
> >     array:set(1, nextevent(), A1),
> >     array:set(2, nextevent(), A1),
> >     array:set(3, nextevent(), A1),
> >     array:set(4, nextevent(), A1),
> >
> >     io:format("~nArray: ~p~n", [A1]),
> >     io:format("Array size: ~p~n~n", [array:size(A1)]).
> 
> 
> Erlang has no mutable data structures.
> Once you have given A1 a value, that value can never change.
> When you call array:set(1, nextevent(), A1),
> you pass three immutable values.
> array:set/3 responds by creating and returning a NEW value,
> which typically reuses bits of its inputs (without changing them
> in any way).  So
> 
> main() ->
>      A0 = array:new(20),
>      A1 = array:set(0, nextevent(), A0),
>      A2 = array:set(1, nextevent(), A1),
>      A3 = array:set(2, nextevent(), A2),
>      A4 = array:set(3, nextevent(), A3),
>      A5 = array:set(4, nextevent(), A4),
>      io:format("Array: ~p~n", [A5]),
>      io:format("Array size: ~p~n", [array:size(A5)]).
> 
> This is absolutely basic to Erlang, and is one of the reasons why Erlang
> is one of the very few languages in which exception handling is worth
> anything: no matter what went wrong, your data structures CANNOT have
> been corrupted.
> 
> Of course you don't want to write a whole list of updates like that,
> which is why you would write
> 
>      A5 = lists:foldl(fun (N, A) -> array:set(N, nextevent(), A end,
>                 array:new(20), lists:seq(0, 4))
> 
> producing the output
> 
> {array,20,0,undefined,
>         {{0,1,4,9,16,undefined,undefined,undefined,undefined,
>           undefined},
>          10,10,10,10,10,10,10,10,10,10}}

Going a step further, into the nature of the task itself...

(This is a long post, anyone uninterested in nitpicking beginner stuff
should skip it...)

In Erlang an "event" can mean a few things, but generally speaking
we are trying to represent external events as messages, which means
that the process that will *react* to a given event is actually
reacting to a *message* of a specific form that represents a given
type of event.

In your example, you instantiate an array. This is a mismatch that
your C background is telling you fits the problem, but very likely
does not. A more basic functional data structure is a list.

DO NOT WORRY ABOUT WHAT HAPPENS IN THE RUNTIME TO MAKE THIS WORK.

[That is rule #1 of using a higher-level language. Or rather, it is
a strong rule at the outset of a project (letting you think mostly
about the essence of the problem rather than the way that manifests
in machine instructions), and then becomes a guideline later once
you have a working system and can prove you actually have a performance
problem of some sort.]

So let's start with a list. We will need to initialize our process
somehow. So let's make a `start_link/0` function that spawns a
function and gives us back its PID so that we can send it messages,
allowing us to directly simulate receipt of those messages from the
shell.

We also need some way of letting the process report back its current
state without getting too wizardly with the runtime (otherwise that
process is a black-hole for data, which probably isn't your intention).

So we wind up with a module, saved in a file "eventest.erl":

  -module(eventest).
  -export([start_link/0]).

  start_link() ->
      spawn_link(fun init/0).

  init() ->
      State = [],
      loop(State).

  loop(State) ->
    receive
      {event, Data} ->
          NewState = [Data | State],
          loop(NewState);
      {report, Asker} ->
          Asker ! State,
          loop(State);
      shutdown ->
          ok = io:format("~p: shutting down. Bye!~n", [self()]),
          exit(normal);
      Unexpected ->
          ok = io:format("~p: Received unexpected messages: ~tp~n",
                         [self(), Unexpected]),
          loop(State)
    end.

This is pretty much the most elementary way of doing things in a
process-based way imaginable in Erlang -- and in this case the *process*
spawned when calling `start_link/0` above is on its own timeline,
independent of anything else in the world, and within that process it
has a list-type data structure that represents its state, and we will
be appending to any time a message of the form `{event, Data}` is received:

This is what playing with it in the shell looks like:


ceverett@REDACTED:~/Code/erlang$ erl                                                                                                                                                                                                                                           
Erlang/OTP 18 [erts-7.2] [source] [64-bit] [smp:2:2] [async-threads:10] [kernel-poll:false]                                                                                                                                                                                  

Eshell V7.2  (abort with ^G)
1> c(eventest).
{ok,eventest}
2> Tester = eventest:start_link().
<0.40.0>
3> Tester ! {report, self()}.
{report,<0.33.0>}
4> flush().
Shell got []
ok
5> Tester ! {event, "foo"}.
{event,"foo"}
6> Tester ! {report, self()}.
{report,<0.33.0>}
7> flush().                  
Shell got ["foo"]
ok
8> EventList = ["bar", "baz", "balls"].
["bar","baz","balls"]
9> SendToTester = fun(Data) -> Tester ! {event, Data} end.
#Fun<erl_eval.6.54118792>
10> lists:foreach(SendToTester, EventList).
ok
11> Tester ! {report, self()}.
{report,<0.33.0>}
12> flush().
Shell got ["balls","baz","bar","foo"]
ok
13> Tester ! "A random message it is not expecting.".
<0.40.0>: Received unexpected messages: "A random message it is not expecting."
"A random message it is not expecting."
14> Tester ! shutdown.
<0.40.0>: shutting down. Bye!
shutdown


And that's it.

So looking at your original case and the super simple model I wrote above,
maybe we want to be able to handle a list of events, perform some transform
over them (or execute some side-effecty operation), and then store them to
the list of events received so far. Maybe we also want to be able to abstract
the details of sending messages behind some function calls as well (so I can
just call `eventest:report(Tester)` instead of always writing out
`PID ! {report, self()}`:


  -module(eventest).
  -export([start_link/0, report/1, single/2, multi/2, shutdown/1]).


  %% Public interface

  start_link() ->
      spawn_link(fun init/0).

  report(PID) ->
      PID ! {report, self()}.

  single(PID, Event) ->
      PID ! {single, Event}.

  multi(PID, Events) ->
      PID ! {multi, Events}.

  shutdown(PID) ->
      PID ! shutdown.


  %% Private functions

  init() ->
      State = [],
      loop(State).

  loop(State) ->
    receive
      {single, Event} ->
          Data = do_processing(Event),
          NewState = [Data | State],
          loop(NewState);
      {multi, Events} ->
          DataList = lists:map(fun do_processing/1, Events),
          NewState = DataList ++ State,
          loop(NewState);
      {report, Asker} ->
          Asker ! State,
          loop(State);
      shutdown ->
          ok = io:format("~p: shutting down. Bye!~n", [self()]),
          exit(normal);
      Unexpected ->
          ok = io:format("~p: Received unexpected messages: ~tp~n",
                         [self(), Unexpected]),
          loop(State)
    end.

  do_processing(Event) ->
      Size = length(Event),
      ok = io:format("~p: Received event of size: ~p~n", [self(), Size]),
      {Size, Event}.


Obviously, the `do_processing/1` function is arbitrary, but you can imagine
a process maintaining its own state that is mutable *in time* but uses
symbols which are *immutable within their scope* to represent the
actual calculations involved in whatever transforms are being performed.

This is a *lot* closer to the way things are often done in "pure" Erlang,
meaning, this is how things are often done in Erlang when using processes
to maintain state, modules to represent a process's definition and hide
implementation from public functions, and messages to represent events
within a system.

There are more details, but I think you're just getting hung up on some
of the very early differences -- and that is totally normal.

As far as functional idioms go... there is a very basic core of operations
one expects to find in pretty much any functional language, and they tend
to be centered around list operations. The Erlang `lists` standard lib
module documentation provides a terse, but pretty solid representation of
them (but also includes a lot of stuff specific to Erlang's own lib).

What you are looking for is list operations like folds, maps and filters,
the basics of recursion (which can seem at first more weird than explicit
looping, but winds up becoming easier than explicit looping over time), and
some of the basic concepts underlying Erlang's style of managing
concurrency.

I wrote a few bits about these basics -- forgive the links, I just don't
feel like re-writing it all here:

An explanation of fold (which Richard was using as an example above):
http://stackoverflow.com/questions/26854586/explanation-of-listsfold-function/26855055#26855055

Erlang processes VS Java threads:
(tl;dr: The difference between Java and Erlang is much deeper than the
essentially cosmetic differences between OOP and FP.)
http://stackoverflow.com/questions/32294367/erlang-process-vs-java-thread/32296577#32296577

Clearing up some misunderstanding about recursion:
http://stackoverflow.com/questions/27234898/erlang-recursive-end-loop/27236236#27236236

A word on "function chaining", touching on error code:
http://stackoverflow.com/questions/34622869/function-chaining-in-erlang/34626317#34626317

Blah blah.

Start messing around with the module above, since it runs. Morph it into
something that does whatever you want. Make two that talk to each other.
Every little experiment will introduce you to new things at first, and
give you better insights into what is being said if you start to read a
book like "Learn You Some Erlang" (free online http://learnyousomeerlang.com/ ),
"Erlang and OTP In Action", "Programming Erlang", etc. (all strong
recommendations).

As for getting a feel for what exists beyond C and asm, and letting your
mind relax and come to terms with abstractions based on primitives, I still
can't think of a better text than "Structure and Interpretation of Computer
Programs" (also free online: https://mitpress.mit.edu/sicp/full-text/book/book.html ).

-Craig



More information about the erlang-questions mailing list