Erlang Programming Exercise: 5-1
It took a while before reading chapter 5 again. I've been off trying to cobble together an Erlang replacement for some of the proxies at work. It's almost ready, it just needs to log the health stats.
When I was reading through the "Programming Erlang" book by Joe Armstrong I ended up doing an exercise very much like Exercise 3-4, but I made a mistake and implemented it as a server, like this exercise. It's fun to compare my styles. In the last version I had a large case statement for the different actions I wanted to do, and each option called a function of the same action name. I'm understanding the behavior design patterns better now and it results in less code written.
In my implementation (below) I used the lists module as much as I could, the one exception being the match function. I couldn't find a lists function that would work. I thought about using lists:filter/2, but that would result in more code because I'd have to write the predicate function and then pull the keys out of the resulting list of tuples.
Since my last post I've also watched all of the Erlang in Practice videos by Kevin Smith. I liked his use of ?SERVER versus ?MODULE when spawning processes and sending messages. He has 5 hours worth of video in 8 screencasts. I highly recommend them to other Erlang noobs out there.
Most of the exercise was straight forward. I had to look up in the man pages the lists, spawn/3 and register/2 function calls. The part that I had the biggest problem with was the spawn call interacting with the init call. I didn't realize that if I wanted to pass an empty array to the init call I had to put it inside the arguments array like so: [[]]. My first attempt was without the inner array and it would crash at runtime. It makes sense now. It is an array of arguments. If you give it an empty array, then there are no arguments and it will look for init/0 instead of init/1.
And the implementation:
-module(my_db).
-export([start/0, stop/0, write/2, delete/1, read/1, match/1]).
-export([init/1, terminate/1]).
-define(SERVER, ?MODULE).
%% [bgh] PUBLIC INTERFACE
start() ->
register(?SERVER, spawn(my_db, init, [[]])),
ok.
stop() ->
?SERVER ! {stop, self()},
receive
{reply, Reply} ->
Reply
end.
write(Key, Element) ->
call(?SERVER, {write, Key, Element}).
delete(Key) ->
call(?SERVER, {delete, Key}).
read(Key) ->
call(?SERVER, {read, Key}).
match(Element) ->
call(?SERVER, {match, Element}).
%% [bgh] start/stop interface
init(Args) ->
loop(Args).
terminate(State) ->
State.
%% [bgh] server handler functions
handle_msg({write, Key, Element}, Db) ->
{ok, lists:keystore(Key, 1, Db, {Key, Element})};
handle_msg({delete, Key}, Db) ->
{ok, lists:keydelete(Key, 1, Db)};
handle_msg({read, Key}, Db) ->
case lists:keyfind(Key, 1, Db) of
false ->
{{error, instance}, Db};
{Key, Value} ->
{{ok, Value}, Db}
end;
handle_msg({match, Value}, Db) ->
{match(Value, Db), Db}.
match(_Element, [], Keys) ->
Keys;
match(Element, [{Key,Element}|Db], Keys) ->
match(Element, Db, [Key|Keys]);
match(Element, [_H|Db], Keys) ->
match(Element, Db, Keys).
match(Element, Db) ->
match(Element, Db, []).
%% [bgh] copy/pasted from page 135, erlang programming
call(Name, Msg) ->
Name ! {request, self(), Msg},
receive {reply, Reply} -> Reply end.
reply(To, Msg) ->
To ! {reply, Msg}.
loop(State) ->
receive
{request, From, Msg} ->
{Reply, NewState} = handle_msg(Msg, State),
reply(From, Reply),
loop(NewState);
{stop, From} ->
reply(From, terminate(State))
end.
Cheers,
Halzy
Silence
The blog has been a bit quiet. I've been rewriting some of our server software at work in Erlang and it's taken up most of my free time. I'm about done with it, so we should see the posts pick up again soon.
Cheers,
Halzy