[erlang-questions] Clueless am I

Joe Armstrong erlang@REDACTED
Tue Apr 26 22:53:37 CEST 2016


On Tue, Apr 26, 2016 at 7:45 PM,  <duncan@REDACTED> wrote:
> One thing that helped me as a beginner wrt the 'immutable variable', and I
> think will help with understanding this whole thread, is that the C mindset
> contains not only the 'mutable variable' but also the 'loop' construct. It's
> important to note there are no loops in erlang. Even though 'loop' is used
> in the post below, it's not a c-like loop - it's a function call.
>
> Erlang uses recursion where C uses loops. And the new instance of the
> recursed function has it's own name space. So it sort of looks like a given
> 'variable' is mutable (ie the variable X is 5 in one instance of the
> function and 6 in the next instance) - but it really is a different variable
> because it's in a different namespace. To a newbie like me, this is like
> 'updating the variable' in the initial post.
>
> C has loop over x, erlang has recurse over x.
>
> Ie the answer to the original question 'how to add elements to a data
> structure' is 'with recursive function calls'. And that avoids the A1 to A5
> in the original program (ie you only need the variable A).
>
> Hopefully this helps more than it confuses. It makes sense in my head but
> never comes out right on paper.

You're quite right.

The other thing to point out is that when I talk about code like

       foo(N, A) ->
             .. do something ...
             foo(N-1, B).

I'll uses the words "loop" - You might think of the final call foo(N-1,B)
as a function call that returns to this place in the code. I see it as a
GOTO that hops to the function entry point.

In code like

      foo(A) ->
             B = x(A),
             C = y(B),
             foo(C).

There is a significant difference between the calls to x, y, and foo.
When we call x(A) we return to the caller (same for y(B)).

 When we call foo(C) we don't actually call foo(C) we replace the
current stack frame containing the local variable A with the variable C and
jump to the top of the code. So it's a loop and executes in finite space without
creating an additional stack frame (and the garbage collector can
reclaim the space used by B since nobody can ever access this) - this
is how we don't
run out of space.

The equivalent code in C (python etc) will create a stack frame and
will crash in an infinite recursion.

We *have* to do it this way to create infinite process loops to store data
like:

   store(Data) ->
         receive
                Msg ->
                     Data1 = update(Msg, Data),
                     store(Data1)
         end.

If the call to store(Data1) was subroutine call that created a new stack frame
we'd eventually run out of space.

Old timers (like me) get so used to this idea that we just call code like this
"loops" - so to me tail recursive code (to give it a name) is just the
FP equivalent of a loop in C - the same is true for all FPLs

Cheers

/Joe

>
> Duncan Sparrell
> s-Fractal Consulting LLC
>
>
> -------- Original Message --------
> Subject: Re: [erlang-questions] Clueless am I
> From: zxq9 <zxq9@REDACTED>
> Date: Tue, April 26, 2016 3:16 am
> To: erlang-questions@REDACTED
>
> 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
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions
>
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions
>



More information about the erlang-questions mailing list