Code Review 5: Bowling Kata, Erlang edition

So for this cycle of Erlang coding, I did a combination of coding and reading.

Code-wise, one of the most pertinent things I tried was extracting constants. My initial pass at this was to create a module with functions that returned the constants, but, as I thought, it lead me to an illegal guard expression compile error:

roll(on_frame, #frame{first_roll=First, second_roll=Second}, that_knocked_down, Pins_Knocked_Down) when (First =:= constants:all_pins_down()) and (Second =:= undefined) -> 
	{frame, #frame{first_roll = Pins_Knocked_Down, value = Pins_Knocked_Down}};

I quickly wised up and, through a combination of the Armstrong book and a Google search, I found that macros are the standard way to achieve constants in this language. Thusly:

-define(TRACE, 1).
-define(INFO, 2).
-define(ERROR, 3).
-define(ALL_PINS_DOWN, 10).
-include("constants.hrl").

roll(on_frame, #frame{first_roll=First, second_roll=Second}, that_knocked_down, Pins_Knocked_Down) when (First =:= ?ALL_PINS_DOWN) and (Second =:= undefined) -> 
	{frame, #frame{first_roll = Pins_Knocked_Down, value = Pins_Knocked_Down}};

I also buttoned-up my hand-rolled logger. Or more accurately, I refined my usage of said logger. As I added more and more informative log messages, I found myself sorely needing some kind of printf analog, and its name is io_lib:format():

roller(
	frame_roll_function, Roll, 
	frame_is_full_function, IsFull, 
	frame_rescore_function, Rescore,
	frame_constructor_function, NewFrame,
	logger_function, Log) when 
	is_function(Roll) and 
	is_function(IsFull) and
	is_function(Rescore) and
	is_function(NewFrame) and
	is_function(Log) ->
	{roller, fun(on_game, #game{frames=Frames} = G, knocked_down, PinsKnockedDown) when Frames =:= undefined -> 
		Log(info, io_lib:format("Rolling a ~w", [PinsKnockedDown]), with_code, ?INFO),
		{game, roll_with_no_frames(G, PinsKnockedDown, Roll, NewFrame, Log)};

	   (on_game, #game{frames=Frames} = G, knocked_down, PinsKnockedDown) when Frames =/= undefined ->
	   	Log(info, io_lib:format("Rolling a ~w", [PinsKnockedDown]), with_code, ?INFO),
	 	{game, roll_with_frames(G, PinsKnockedDown, Roll, IsFull, Rescore, NewFrame, Log)}
		
	end}. 

I had to look up the ~w switch in favor of the ~p in order to keep the log message all on a single line. ~p includes “sensible” indentation.

Next on the agenda was to replace anonymous underscore variables with “named” anonymous variables, in the name of readability a la Armstrong:

roll(on_frame, #frame{first_roll=First} = Frame, that_knocked_down, Pins_Knocked_Down) when First =:= undefined ->
	{frame, Frame#frame{first_roll = Pins_Knocked_Down, value = Pins_Knocked_Down}}; 
roll(on_frame, #frame{first_roll=First, second_roll=Second}, that_knocked_down, Pins_Knocked_Down) when (First =:= ?ALL_PINS_DOWN) and (Second =:= undefined) -> 
	{frame, #frame{first_roll = Pins_Knocked_Down, value = Pins_Knocked_Down}};
roll(on_frame, #frame{second_roll=Second, value=V} = Frame, that_knocked_down, Pins_Knocked_Down) when Second =:= undefined ->
	{frame, Frame#frame{second_roll = Pins_Knocked_Down, value = V+Pins_Knocked_Down}};
roll(_on_frame, _Frame, _that_knocked_down, _Pins_Knocked_Down) -> 
	{error, frame_already_has_two_rolls}.

There actually weren’t very many other occurrences of this, but they are now gone.

I did do some reading in the book about Erlang concurrency. Eventually, I will move on to the generic server, but I want to understand the basics first. I have a tendency for that sort of thing.

When I felt I had read too much, I put the book down and started on the scoring server:

-module(score_server).
-export([
	start/2
]).
-include("constants.hrl").
-include("game.hrl").

start(logger_function, Log) when is_function(Log) -> 
	spawn(fun() -> main_loop(Log) end).

main_loop(Log) ->
	receive
		{From, {score_of, Game}} when is_record(Game, game) ->
			Log(info, io_lib:format("Server received score_of message: ~w", [Game]), with_code, ?INFO), 
			ScoreOf = get_scorer(),
			From ! {self(), ScoreOf(Game)},
			main_loop(Log)
	end.
	
get_scorer() ->
	{scorer, Scorer} = game:scorer(value_of_a_frame()),
	Scorer.
	
value_of_a_frame() -> fun frame:value_of/1.

So that is my first pass. And I interacted with it as follows:

Erlang (BEAM) emulator version 5.6.5 [smp:2] [async-threads:0]

Eshell V5.6.5  (abort with ^G)
1> {logger, Log} = logger:new("testlog.txt").
{logger,#Fun<logger.0.60591536>}
2> rr(game).
[frame,game]
3> MyGame = #game{frames=[]}.
#game{frames = [],score = undefined}
4> Server = score_server:start(logger_function, Log).
<0.36.0>
5> Server ! {self(), {score_of, MyGame}}, receive {Server, Response} -> Response end.
{score,0}

For next time:

  • I still have native coverage analysis on my back burner.
  • I need to find a way to log process ID.
  • I want to write a client of the scoring server so that I can have many clients and servers talking at the same time.
  • Add more functionality to the server.
  • Eventually get to reading about the generic server.

Now if you’ll excuse me, my scrum team needs me to be the villain at Sprint Zero.

Announcer: You’re reading the EIP web-ring.
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: