Dev Corner

Sunday, January 22, 2006

List Comprehensions

As I was trekking through yaws country, I realized the value that the recent(sort of) feature of list comprehensions are. I'm still getting used to them all the time. I find myself writing a recursive function to iterate over data using some of the functions in the lists module, or even forgetting about map, fold(l|r) etc...

I was writing a page that contacts an erlang node in order to display memory data for the VM.

The function to get the remote data is:


get_memory_data(Server) ->
case rpc:call(Server, erlang, memory, []) of
{badrpc, Reason} ->
{error, Reason};
Data ->
{ok, Data}
end.


I use the most excellent rpc module to easily execute a remote method call to a remote erlang node, the name of which is passed in as a parameter.

What I wanted to do with this page is to output the memory information in a simple table. In order to do this in yaws, I wrote an tag that outputs this information dynamically.

<erl>

out(A) ->
Data = yaws_api:parse_query(A),
{ok, Server} = wsform:get_value_atom("server", Data),
{ehtml, output_memory(Server)}.

</erl>

Notice that I'm using the form library;)

What I did initially was this:


output_memory(Server) ->
case get_memory_data(Server) of
{ok, Data} ->
Fun = fun({Name, Value}) ->
{tr, [], [{td, [], f("~p", [Name])},
{td, [], f("~p", [Value])}]}
end,
Rows = lists:map(Fun, Data),
{'div', [{style, "float: left; padding-right: 10px;"}],
{table, [], [{tr, [], {td, [{colspan, "2"}], "Memory Stats"}},
Rows]}};
{error, Reason} ->
{p, [], f("No memory data: ~p", [Reason])}
end.

This function gets the memory data and loops over the list to create a tr and two td tags for each memory statistic. It uses lists:map to do this

But then I remembered list comprehensions. I then wrote:

output_memory(Server) ->
case get_memory_data(Server) of
{ok, Data} ->
Rows = [{tr, [], [{td, [{class,"dataHeader"}], f("~p", [Name])},
{td, [], f("~p", [Value])}]} || {Name, Value} <- Data],
{'div', [{style, "float: left; padding-right: 10px;"}],
{table, [], [{tr, [], {td, [{colspan, "2"}, {class, "twocolDataHeader"}], "Memory Stats"}},
Rows]}};
{error, Reason} ->
{p, [], f("No memory data: ~p", [Reason])}
end.

I've highlighted the list comprehension line of code. Now I don't have to define my own anonymous fun.

Is there any difference? Sematically, no I don't believe, but I think you will find that list comprehensions will simplify list iteration code in the long run. They are used in qlc queries too.

I will cover qlc and mnesia in another blog entry. I thought that I should put another yaws entry out there before I go on to postgresql and mnesia.

One more thing, I did write a parameter validation block using the form validation library, which I placed before the memory output block. Here it is:

<erl>
out(A) ->

Rules = [{"server", [required]}],

Data = yaws_api:parse_query(A),
case wsform:validate_page_data(Data, Rules) of
ok ->
ok;
Error ->
[{html, f("Invalid data: ~p ~p", [Error, Data])},
break]
end.


</erl>

Returning ok from an out function is a noop. It does nothing and continues the page. If there is an error, a cryptic programmer-friendly error message is displayed and the page processing is stopped via the break atom.

See you next time.

2 Comments:

  • Nicely done. I find that my best learning coming from the non-Erlang world is through seeing such refactoring (well this might be technically another concept, but it is similar enough for me). I see how I would do it, and how it could be done better, and I learn. Thanks!

    By Anonymous Anonymous, at 3:53 PM  

  • If you are using the ehtml module of the yfront application in jungerl; then you could have written
    something like this:


    case get_memory_data() of
    {ok, Data} ->
    ehtml:table([[Name,Value] || {Name,Value} <- Data]);
    ...

    Then put this within a 'div' and use some CSS to make it look the way you like.

    By Anonymous Anonymous, at 6:48 AM  

Post a Comment

<< Home