[erlang-questions] Avoiding boilerplate code when using gen_server

Joe Armstrong erlang@REDACTED
Wed Mar 21 20:21:43 CET 2012

On Wed, Mar 21, 2012 at 4:29 PM, Garrett Smith <g@REDACTED> wrote:
> On Wed, Mar 21, 2012 at 6:48 AM, Torben Hoffmann
> <torben.lehoff@REDACTED> wrote:
>> Hi,
>> I was sitting with a co-worker - who is proficient in OCaml and lots of
>> other stuff and he is now learning Erlang - and then he comes up with a good
>> question on the amount of boilerplate code when implementing a gen_server.
> I've had the exact same conversation with coworkers -- it led to this:
> http://e2project.org/
>> From the API functions you call out to gen_server:call/2 and on top of that
>> you need to implement a handle_call/3 function clause that matches whatever
>> format you used for wrapping the incoming message.
>> The question was: why don't you use an IDL (Interface Definition Language)
>> approach instead of writing all that code that has to match up in order to
>> work?
> Erlang has a "parse transform" feature that would let you embed such
> an "IDL" in a single module, and could generate the "boilerplate".
> I initially started down this road with e2, but decided against it for
> these reasons:
> - Erlang is already a *very* simple language - let's just use it
> - Auto generated functions are hard to document, test, etc.
> - Hiding the client-server distinction in Erlang is a terrible disservice
> IMO, e2 solves the problem of "too much boiler plate". It's really
> easy and requires zero magic.
> Here's a "gen_server" equivalent in e2:
> https://github.com/gar1t/e2v2/blob/master/examples/ping/src/ping_server.erl

That's nice - but one could with a little thought and a few conventions
make this even shorter. Something like:


    ping(_From, State) -> {reply, pong, State}.

And just let all undefined values default to sensible values.
As in your version you could use

    init() -> State to prime the state

A counter would just be:

    -interface([inc/1, dec/1, get/0])
    init() -> 0.

    deposit(_From, X,N) -> {reply,ack,N+X}.
    withdraw(_From,N) ->
    get(_From, N) -> {reply,N,N}.

> You might object to the separation of "ping" and "handle_msg" -- it
> appears to be just one function, so why break it up into two pieces?
> The problem is that it's not one function -- it's definitely *two*
> very separate pieces of code.
> When you write a gen_server style module, you're writing code that
> support client-server interactions. You have, in the same module, code
> that is called by the "client" and code that is called by the
> "server". If you don't grok this, you're missing the entire point of
> this type of module.
> Code that hides this difference -- e.g. a parse transform that gloms
> the client and server functions into one -- is IMO a Really Bad Idea.
> As an example of how this client/server difference is important,
> consider form validation in a web app. There are two places you can
> run code to validate form input -- you can validate it in the browser
> using JavaScript, or you can send the form data to the server. Web
> developers may opt for browser validation to avoid the expense of
> sending data to the server -- or they might prefer it on the server.
> Either way, it's an important consideration.
> This same dynamic applies to gen_server style modules. If you stick
> with it, I think you'll appreciate the separateness of client and
> server facing code.
> As for the boilerplate, I couldn't agree with you more!
> Garrett
> P.S. I'm giving a talk on e2 at the SF Factory in a couple weeks,
> where I'll get into this in more details. Also, the e2 docs and github
> project are in slight disarray at the moment, but will be put in order
> this weekend!
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://erlang.org/mailman/listinfo/erlang-questions

More information about the erlang-questions mailing list