[erlang-questions] Erlang and Akka, The Sequel

Joe Armstrong erlang@REDACTED
Fri Apr 3 11:13:33 CEST 2015


On Thu, Apr 2, 2015 at 9:42 PM, Youngkin, Rich
<richard.youngkin@REDACTED> wrote:
> Thanks Fred for clarifying the example in your original response.  Thanks
> too to OK for the additional information, more below.
>
> Cheers,
> Rich
>
> On Mon, Mar 30, 2015 at 7:39 AM, Fred Hebert <mononcqc@REDACTED> wrote:
>> On 03/31, Youngkin, Rich wrote:
>>>
>>>    try
>>>        {ok, Cs} = adventure:collect_coins(),
>>>        Res = lists:flatmap(fun(Coins) ->
>>>            {ok, Val} = adventure:buy_treasure(Coins),
>>>            Val
>>>        end, Cs),
>>>    of
>>>        SuccessValue ->
>>>            %% Do something like continue to next challenge
>>>    catch
>>>        Type:Reason ->
>>>            %% Do something like maybe repeat the previous challenge
>>>    end.
>>> ...
>>>    try
>>>        lists:flatmap(fun(Coins) -> adventure:buy_treasure(Coins) end,
>>>                      adventure:collect_coins())
>>>    of
>>>        SuccessValue -> % Keep going
>>>    catch
>>>        _:_ -> % alt path
>>>    end
>>>
>>
>> >> I don't see a difference between the previous 2 Erlang implementations.
>> >> Can
>> >> you elaborate?
>>
>
>> One of them matches on `{ok, Value}' to cause a failure, which makes the
>> assumption 'bad' cases are returned
>> as `undefined' or `{ok, Error}'. In the latter case it is expected that
>> the called code raises exceptions when something
>> goes wrong.
>
> Got it, thanks!
>
> On Wed, Apr 1, 2015 at 5:56 PM, <ok@REDACTED> wrote:
>>
>>
>> I find the Java example compelling:  if I want some threads
>> communicating in a particular way in Java, as of 1.8 it is
>> easier to go back to basics than to read all the library
>> documentation and figure out what's going on.
>>
>> What saves the OTP behaviours from the same fate?
>> Mainly the fact that there aren't very many of them,
>> and the number isn't growing.  Erlang/OTP seems to strike
>> a very nice balance between a core of simple Erlang
>> primitives and a limited palette of "big" behaviours that
>> take a lot of the work off your hands.  Futures fall into
>> the "much needed gap".
>
>
> Interesting point. One of the things I like about Erlang is it's simplicity.
> I don't quite follow what you mean by "Futures fall into the "much needed
> gap"". Are you saying that futures should, or should not, be added to
> Erlang/OTP? I'm thinking not.
>
>>
>>
>> When you might reach for a FutureTask<T> in Java, in
>> Erlang you would think about what the processes are
>> doing and how they communicate and you would look at the
>> constellation of communicating processes and you might
>> very well end up thinking that the Future-style
>> communication pattern was *not* after all the best.
>>
>
> Agreed. Understanding the problem and picking an appropriate solution is
> always the best approach. Blindly implementing a solution, either because
> you don't really understand the problem or because something is cool (e.g.,
> futures), is never good.
>
>>
>> > Garrett Smith gave a great presentation at last week's Erlang conference
>> > about "The Timeless Way of Building Erlang Apps" (see [3] and
>> > erlangpatterns.org). What I think could be useful is a "pattern"
>> > description for futures that incorporates the implementation using RPCs.
>> > I
>> > may just do this if someone doesn't beat me to it.
>>
>> For me, the single most important piece of advice is
>> DON'T THINK IN TERMS OF FUTURES AND THEN ENCODE THAT IN ERLANG.
>> Think in terms of processes and the communication between them.
>> Consider alternative communication architectures.
>
>
> One of my motivations in starting this email thread is due to my relative
> newness to Erlang. As might be apparent from some of my questions, I don't
> necessarily have a good understanding of the best practices for solving a
> given class of problems. For someone like me it would be good to at least
> have code examples of how to implement certain best practices. These
> examples could also include information on what problems they might not be
> appropriate to for. Regarding futures, I'm assuming, perhaps mistakenly,
> that they could be useful in some Erlang applications. Regarding best
> practices, this is the area that I believe erlangpatterns.org is intended to
> address.
>
> To reiterate what I said above, one shouldn't blindly implement a solution
> without considering when it is appropriate and when it isn't appropriate.
> This is what I think you meant when you stated above "DON'T THINK IN TERMS
> OF FUTURES AND THEN ENCODE THAT IN ERLANG".

Promises, futures and so on are extremely easy to implement in Erlang.
This is how I explain things when I teach Erlang:

We'll start with an RPC - we can write this in many ways.
One way might be:

   rpc(Pid, Query) ->
    Ref = make_ref(),
    Pid ! {self(), Ref, Query},
    receive
      {Ref, Response} ->
          Response
    end.

The server that receives this message does something like:

  receive
     ...
     {From, Ref, Query} ->
         Response = ...
            From ! {Ref, Response}
     ...
  end,

This basic pattern is repeated all over the place is with many small
variations.

<aside>
*Remember this pattern* (repeat 100 times) - this should dance out of your
fingertips and not require conscious thought
</aside>

For example, all the gen_server does is wrap this pattern
with a few convenience functions.

Now keeping the server code unchanged we can modify the RPC

Start with the original (and stare at the added comment):

   rpc(Pic, Query) ->
    Ref = make_ref(),
    Pid ! {self(), Ref, Query},
    %% watch this space **************
    receive
       {Ref, Response} ->
          Response
    end.

Now I'll rename rpc as rpc1 and split it into two functions at the
comment:

   rpc1(Pid, Query) ->
    Ref = make_ref(),
    Pid ! {self(), Ref, Query},
    Ref.

    wait(Ref) ->
      receive
        {Ref, Response} ->
           Response
      end.

So obviously

    rpc(Pid, Query) ->
       Ref = rpc1(Pid, Query),
       wait(Ref).

How about some renaming? I'll call rpc1 "promise" and wait "yield"

So

   promise(Pid, Query) ->
    Ref = make_ref(),
    Pid ! {self(), Ref, Query},
    Ref.

    yield(Ref) ->
     receive
       {Ref, Response} ->
          Response
    end.

<aside>we've invented futures :-) </aside>

Now we can do something in the gap between the promise and the
yield:

    compute_something(...) ->
         P1 = promise(...)
         Val1 = ... some local computation ...
         Val2 = yield(P1),
         ...

So now Val1 and Val2 are computed in parallel.

(We've now invented one of the basic mechanisms for parallel programming
 this might appear as parbegin ... parend in some programming language :-)

The *reason* why Erlang does not have futures/promises/ .. or whatever else
you might like to call them is that they are trivially implemented
in a few lines of code using just spawn/send/receive.

In languages that are basically sequential this is not possible - that's why
it's a big deal (TM) to have libraries or language features to support
this.

And now for the tricky part ....

*Broken Promises* - Remember that scene in Casablanca when Iisa
confronts Rick, this is all about broken promises. Fulfilled promises
are easy to deal with, but we must ask what happens if the server
crashes and never sends back a message? Now life gets difficult, and
as Rick found out the consequences of a broken promise lead to all
sorts of problems...

In Erlang, dealing with broken promises is possible (though not easy)
using links, monitors and by trapping exits.

The spawn/send/receive group of primitives are used to program the non
error cases where things don't go wrong.

trap_exits/links/monitors deal with error cases where things go wrong.

The gen_servers, supervision trees and so on just wrap these primitives
in combinations that we have found useful for  solving "common" problems.

In cases where the library behaviors don't do *exactly* what you want
it's often easier to "roll you own" rather than shoehorning your problem
into the wrong solution.

The reason there are not a load of design patterns is that we don't need them.
We do need to teach the basics though.

spawn/send/receive are as basic to Erlang as for/if/case are to
sequential programming

This is where we have a problem - in sequential languages nobody bothers to
teach what for/if/case/switch/while etc do - it is "implicit knowledge that all
programmers have" (I'm excluding total beginners here) -

Experienced Erlang programers know the power of spawn/send/receive so
rarely bother
to explain how to build things with these primitives.

My advice would be to stare hard at spawn/send/receive and *memorise*
the RPC pattern
and the basic cleint/server setup. Then understand links.

Write things without using the libraries - then learn the libraries.

Note I said you can write RPC in "many" ways - what did I mean by this?

To illustrate, here's are some variations:

rpc(Pid, Query) ->
   Pid ! {self(), Query},
   receive
     {Pid, Response} ->
         Response
   end.

or

rpc(Pid, Query) ->
    Pid ! {self(), Query},
    receive
      {Pid, Response} ->
          Response
      after 10000 ->
        exit(timout)
     end.

or

rpc(Pid, Query) ->
   Pid ! {self(), Query},
   receive
     {Pid, Response} ->
        {ok, Response}
     after 10000 ->
        {error, timeout}
end.

You can see how the basic pattern remains - we can add timeouts
etc. but then we have to decide what to do with the variants.

Only the most common patterns are found in the gen_server
so it's a good idea to understand the basic pattern and
modify it to your specific needs.


/Joe


>
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions
>



More information about the erlang-questions mailing list