[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:
-module(ping_service).
-behavior(e2_service).
-interface([ping/0]).
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:
-module(counter).
-behaviour(e2_service).
-interface([inc/1, dec/1, get/0])
init() -> 0.
deposit(_From, X,N) -> {reply,ack,N+X}.
withdraw(_From,N) ->
{reply,ack,N-1}.
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