On 06/27/2012 03:15 AM, Steve Davis wrote:
> Is it just me, or is hidden state in a function that appears to the
> client as stateless always bad news... eventually?
Yes, but this isn't about state. That's what makes it different from
object oriented programming. Once you create M=mymodule:new(42),
M is always the same M. Nothing that anybody else does can change the
module instance behind your back. This is just as referentially
transparent as the following:
make_adder(X) -> fun (Y) -> Y+X end.
add(A,B) ->
F = make_adder(B),
F(A).
Using parameterized modules, this could be written
-module(adder, [X]).
-export([add/1]).
add(Y) -> Y+X.
-module(foo).
add(A,B) ->
Adder=adder:new(B),
Adder:add(A).
The main difference between funs and parameterized modules are that
modules allow you to have several differently named entry points with
possibly different arities. But you can do that with funs as well if you
want; it's just more clumsy:
make_stepper(C) -> fun ({forward, N}) -> C*N;
({backward, N}) -> -C*N
end.
compare:
-module(stepper, [C]).
-export([forward/1,backward/1]).
forward(N) -> C*N.
backward(N) -> -C*N.
Also important is that parameterized modules are by design not tied to a
particular version of the code (they always call the latest version of
the module). Funs don't survive reloading the code for the module twice,
but a module instance can be kept around for a long time.
A caveat is that (just like funs) existing instances stop working if you
change the interface and reload the code - for example, changing
-module(foo, [X]) to -module([X,Y]). That's why I recommend that client
code should probably never call your_module:new(...) directly, but
always via some kind of facade, so clients don't know the actual names
of the modules they instantiate and thus you can update the module name
along with the interface if you need to to that sort of incompatible
change: e.g., "make_mod(X) -> mod_v1:new(X)." becomes "make_mod(X,Y) ->
mod_v2:new(X,Y)."
/Richard