
My favorite Erlang program (2013) - cprecioso
https://joearms.github.io/published/2013-11-21-My-favorite-erlang-program.html
======
manthideaal
You can find more information about erlang in this Ph.D Thesis (1), on page 86
is an example of a universal server, explained with detail.

[https://erlang.org/download/armstrong_thesis_2003.pdf](https://erlang.org/download/armstrong_thesis_2003.pdf)

~~~
staticassertion
The thesis is, in general, excellent and well worth a read if you're
interested in software reliability even outside of Erlang. The first few
chapters are just a great read for any developer I think.

------
kitd
Excuse my Erlang ignorance.

When he sends the "{become, F}" message, and F is a function pointer or
closure, how is that dispatched across a network? Do the code definitions need
to be present remotely too? If so, I'm not sure that quite qualifies as a
"universal" server, only a server for locally defined functions. Still cool
though.

BTW, I've used a similar technique to create a pool of worker goroutines in
Golang. Had good performance from it too.

~~~
yetihehe
That function's code is serialized and sent to remote node along with
necessary data.

In erlang, if you want, you can compile whole modules from source constructed
at runtime and they behave just like normal code. Typical usage is hot-loading
new modules from disk or from other nodes, you load your patch on one node and
distribute it over network and if you don't make something stupid, your code
is updated in whole system without dropping any tcp connections or the like.
Everything happens automatically and every process using old code is updated
as soon as possible to new version when possible.

~~~
jerf
If you've ever looked at Erlang's data model briefly and gone "what the heck
is with the immutability and lack of user-defined structures, etc.", have
another look at it from this point of view of distribution. It's the secret
decoder ring of "why did Erlang do it that way?"

With another ~25 years of collective experience, if somebody took a shot at it
today I think we could make some improvements, but it was pretty far ahead of
its time, and can still pull some stunts almost nothing else can, or at least
not without creating "an ad-hoc, informally-specified, bug-ridden, slow
implementation of half of [Erlang]", to tweak Greenspun a bit.

~~~
afiori
There was a nice programming language (unison) posted here some weeks ago that
was based on pushing this to the extreme

[https://news.ycombinator.com/item?id=9512955](https://news.ycombinator.com/item?id=9512955)

What I understood was that objects/function are referred by hash of the
source/dependency tree so that updates can be propagated transparently.

------
Jtsummers
Here are a couple previous discussions (only linking ones with comments):

[https://news.ycombinator.com/item?id=12396420](https://news.ycombinator.com/item?id=12396420)
(38 comments)

[https://news.ycombinator.com/item?id=8807660](https://news.ycombinator.com/item?id=8807660)
(2 comments)

------
ProfHewitt
Unfortunately, Erlang does _not_ directly implement Actor behavior change.

Consequently, Erlang cannot directly implement Actor behavior change. A
factorial server is implemented below:

    
    
        FactorialServer[ ] *implements* ProcedureServer<[NaturalNumber], NaturalNumber>
         [i] |->           // received message [i]
            Factorial.[i]  // return result of sending [i] to Factorial
    

In order to implement Actor behavior change, Erlang must resort to using
helper processes that trampoline back so that helper processes can return the
desired change.

See the following for direct implementation of Actor behavior change:

    
    
        https://papers.ssrn.com/abstract=3418003
    

PS. The type ProcedureServer above can be defined as follows:

    
    
        ProcedureServer<t1, t2> ≡ // Procedure server with parameters types t1 and t2 is defined to be
            t1 -> t2 // type of procedure from t1 into t2

~~~
gmfawcett
By "Actor" I see that you're referring to a particular theory named "Actor",
you're not using the term in a colloquial sense.

You seem to be saying that Erlang can implement "Actor behaviour change", just
not directly. In what sense is this a concern? i.e., what value does direct
implementation add here?

~~~
ProfHewitt
Yes, Actor is used in the technical sense axiomatized up to a unique
isomorphism in the linked article and not in the common usage of a thespian
;-)

Erlang not being able to directly express Actor behavior change means that
Erlang programs are more obscure and convoluted.

For example, consider the following program:

    
    
        ProcessorController[pk:PublicKey] *implements* controller 
            // initialize processor controller with a public key pk
        currrentVersion ≔ 1 // processor current version number is initially 1
        running ← Crowd [1] // running is a crowd of at most 1 running activities
        boot[*itemIs* i *whichIs* Package[*imageIS* code, *versionIs*  packageVersion], 
             signedIs s] ↦ // received boot request with item i and signed s         
             *IsEmpty* running *cases* // Check if the processor is running                      
                 True ⇾  // If not running, then
                    SigningChecker.[*itemIs* i, *signedIs* s, *publicKeyIs* pk] *cases* 
                    // check that i was signed by s with private key for pk
                       True ⇾  // If signing check succeeds, then   
                        currentVersion⩽packageVersion *cases*  
                          // check processor current version is
                           // less or equal than package version
                           True ⇾  // If version check succeeds, then
                            (currentVersion ≔ packageVersion; // first update current version number,
                             code.run thru running)    
                             // afterward run code in a hole of mutual exclusion passing
                             // thru running then returning a termination report 
                           False ⇾  // If version check fails,
                                *Throw* BadVersionException[]      // throw bad version exception
                 False ⇾   *Throw* BadSignatureException[] // If signature check fails,
            False ⇾ // If already running, then
               *Throw* AlreadyRunningException[] // throw already running exception
        shutDown ↦   // received shut down request  
            *IsEmpty* running *cases*  // Check if the processor is running        
                   True ⇾  // If not running, then
                         *Throw* NotRunningException[] //throw not running exception 
                    False ⇾  // If already running, then
                         *Cancel* running // cancel running returning Void
        
    

The challenge in Erlang is to implement the hole in the region of mutual
exclusion in the above implementation.

~~~
toast0
How about something like this? I don't understand the code you wrote exactly,
and I smooshed the processor into the controller, because it simplifies
things.

    
    
       fun Self (PublicKey) ->
          CurrentVersion = 1,
          receive
             {upgrade, Function, Version, Signature} -> 
                 case sign_checker(PublicKey, Function, Version, Signature) of
                    true when Version > CurrentVersion -> Function(PublicKey);
                    true -> throw(bad_version);
                    false -> throw(bad_signature) 
                 end;
             shutdown -> ok; 
             _Other ->
                % here's where you handle work
                % But I don't know what work you wanted to do,
                % so I'm just looping
                Self(PublicKey)
        end end.
    

You probably don't actually want to throw for these errors (why should the
server stop if it got an invalid request?), and you probably want your
messages to include a reply tag, so you can get the results (like
gen_server:gen_call/2,3 does), but you know, examples on the interwebs; don't
run them as-is.

~~~
ProfHewitt
The signature for a controller to implement is as follows:

    
    
        Controller *interface*
           boot[*itemIs* Item, *signedIs* Signed] → TerminationReport  
               // boot request with an item and signed returns
                 // a termination report from the boot request
           shutDown → Void     // shut down request returns Void     
    

The challenge in Erlang is to allow the _shutDown_ request to be received even
though the _boot_ request has not yet returned with a termination report.

The cancellation exception is caught within the implementation of the _run_
request and turned into a termination report.

PS. The definition the type Item is as follows:

    
    
      Item ≡ Package[*imageIs* Binary, *versionIs* Version]

~~~
toast0
Oh, I think I see. How about

    
    
       fun Self (State) ->
          {PublicKey, Ref, Function} = case State of
              {P, R, F} when is_ref(R), is_function(F) -> {P, R, F};
              Other -> {Other, false, false}
          end,
          CurrentVersion = 1,
          receive
             {upgrade, Function, NewVersion, Signature} when Ref == false -> 
                 SignRef = make_ref(),
                 async_sign_checker({self(), SignRef}, PublicKey, Function, NewVersion, Signature),
                 Self({PublicKey, SignRef, Function, NewVersion);
             {upgrade, _, _, _} -> throw (no_concurrent_upgrades);
             {sign_check, SignRef, true} when Version > CurrentVersion -> Function(PublicKey);
             {sign_check, SignRef, true} -> throw(bad_version);
             {sign_check, SignRef, false} -> throw(bad_signature);
             {sign_check, _, _} -> throw(bad_sign_check);
             shutdown -> ok; 
             _Other ->
                % here's where you handle work
                % But I don't know what work you wanted to do,
                % so I'm just looping
                Self(PublicKey)
        end end.
    

assuming async_sign_check sends back something sensible {sign_check, SignRef,
true}. async_sign_check would send the work to another process, if you want it
to stop checking the signature in case of early shutdown, the processes could
be linked.

Although, I'm still not quite sure I understand the system you're trying to
get to. This in the context of {become, F}, so I'm assuming you want the
process receiving the boot request to become the requested function. But you
also want a termination report; and I'm assuming the function isn't expected
to terminate, it's expected to receive and process requests. But, you didn't
tell us where you want these reports sent?

In my updated code, there is certainly a possible window where the signature
checker finished near the same time as a shutdown request was sent. In that
case, if the shutdown request is received first, the process would shutdown;
if the signature result is received first, that would be processed, and if the
signature result and versioning was good, the process would become the new
function, and that function would receive the shutdown request. If the new
function has some complicated setup, and you wanted to shut it down in the
middle of that, you would most likely need to use erlang:exit(Pid, kill);
although, a carefully written function could periodically check for a shutdown
message in the middle of startup. I don't find that a terribly useful usecase
--- if you started something, let it finish starting, or just brutally kill
it; architecting a clean shutdown from a partial start is most likely to be
wasted effort, instead, one should architect their start so it doesn't perturb
the state of the world until it is fully started and can be expected to
shutdown cleanly.

~~~
ProfHewitt
Looks like you are still not implementing the signature of a Controller, which
as follows:

    
    
        Controller *interface*
           boot[*itemIs* Item, *signedIs* Signed] → TerminationReport  
               // boot request with an item and signed returns
                 // a termination report from the boot request
           shutDown → Void     // shut down request returns Void     
    

A _boot_ request checks the item which is a Package with code and
packageVersion and then runs the code. The _boot_ request returns a
termination report that results from running the code.

However, when the _boot_ request is still operating, the controller might
receive a _shutDown_ request, which will cause the cancellation of the running
code that will caught within the _run_ request and turned into a termination
report.

Note that there is no timing error in the Actor implementation because, a
_shutDown_ request cannot be received until code. _run_ has released the
region of mutual exclusion.

~~~
toast0
OK, so this isn't really {become, F}, this is {run, F}.

The function F, is expected to return results and terminates, so it is no
longer running (but, it could have spawned a process or otherwise changed the
world state; Erlang is generous with respect to side effects)

I don't really understand the version number means in this case? You can only
run functions with the same or greater versions than had previously been run?

I think you're somehow asking for this to be both synchronous, in the sense
that the result of running the function is returned directly and asynchronous
in that you'd like to be able to cancel it. If you want the return directly, I
can't also have previously returned a cancellation id.

If you just want to run a function, and get the results back, that's not that
hard; but before I write the code for that, I want to really be sure what
you're asking for.

~~~
ProfHewitt
With respect to your question: " _I don 't really understand the version
number means in this case?_" An item can be booted only if its version number
has not regressed in to prevent replay attacks.

There are no cancellation ids in the Actor implementation, however a
cancellation exception can be thrown when an activity is cancelled.

------
holtalanm
RIP Joe Armstrong.

~~~
sergiotapia
It's incredible how someone with so much knowledge and programming experience
still came down to help newbies in the Elixir forum. He was one of the big
reasons why I started learning Elixir and owe the career pivot to him. God
bless you Mr. Armstrong.

~~~
holtalanm
i wish i could write Elixir more on a daily basis. my area is pretty heavily
entrenched in the Java / .NET world.

------
quotha
My favorite erlang-based blockchain

[https://aeternity.com/](https://aeternity.com/)

