programming tip: a note on encapsulation
Joe Armstrong
erlang@REDACTED
Wed Nov 18 09:56:55 CET 2009
Here's a little programming technique I discovered yesterday, which I
rather like. This is a method of hiding information using a higher
order function, which is applicable to the callback style of
programming that is used in things like gen_server.
The basic idea is to replace code like this:
foo({tag1, P, Q}, Var1, Var2, Var3) ->
...
XX = ... Var1 ...
foo({tag2, _, _}, Var1, Var2, Var3) ->
...
with code like this:
foo({tag1, P, Q}, F) ->
...
XX = ... F(var1) ...
In this code the first argument to foo is a pattern which must be
matched, but arguments 2,3,4 etc. are just variables that are not used
for pattern matching. They just define a global environment. Our
problem is to represent this environment in a convenient manner.
The code I was writing was for a little web server that I needed.
The semantics of the web-server were provided by plug-in modules,
I would say something like:
web_server:start(myMod)
This would start a web server whose semantics were determined by the
plug-in module myMod.
myMod had to export number of pre-defined functions, in particular
there was a routine handle/4. handle had 4 arguments
handle(Uri, Args, Pid, Root)
So when I tried to fetch http://localhost:3000/some/page?a=1&b=2
I'd call:
handle("/some/page", [{"a","1"},{"b","2"}], Pid, Root)
Pid was the Pid of a database handler, and Root the root of
a file system where the web server was installed.
To access the data base I'd call lib_db:read(Pid, Key) and
lib_db:write(Pid, key, Value).
So a typical callback clause might look like this:
handle("/counter", [{"name",N}], Pid, Root) ->
Val = lib_db:rpc(Pid, {get, {name, N}}),
TemplateFile = Root ++ "/abc.template",
Result = expand_template(TemplateFile, [{"counter", Val}]),
{ok, html, result}.
So far so good - the problem with this is that all callback routines
need the extra two arguments (Pid and Root) whether of not they use them.
The code in the server that called the handler looked like this:
Mod:handle(Uri, Args, Pid, Root)
By using a higher order function we can make a much neater interface.
We change the server code so that it reads:
F = make_interface(Pid, Root),
Mod:handle(Uri, Args, F),
...
Where
make_interface(Pid, Root) ->
fun(root) -> Root;
(X) -> lib_db:rpc(Pid, X)
end.
Now we can change the client callback code to:
handle("/counter", [{"name",N}], F) ->
Val = F({get, {name, N}}),
TemplateFile = F(root) ++ "/abc.template",
Result = expand_template(TemplateFile, [{"counter", Val}]),
{ok, html, result}.
This *reduces* the complexity of handle. The Pid and Root arguments
vanish and the dependency to the module lib_db is eliminated. Any
number of additional arguments to handle can be hidden inside F.
More information about the erlang-questions
mailing list