Browsing the archives for the erlang tag.

Try, Try Again – What a Little Code Can Mean

work safe

I have started my Erlang based MUD a few times. The first time, I got bogged down in telnet details. The second time, I got lost building far too many proxy objects and indirections. Then I wasn’t even sure what I’d written was correct or that my changes wouldn’t break something.

I was having a hard time figuring out how to drive out the code. Where do I start?

This time around, I’ve gotten farther than I ever had before. I’m at a stage were I can move my character from one room to another. The perspective this time, I got a chance to drive the code out by running it via the REPL. I found a happy medium between top down and bottom up. This is similar to TDD use to test out an View-Model. My REPL based code had simple functions that mimicked the future texted based input. Seems like a duh now that I’ve said it. It really does feel more like I’m cutting with the grain.

But now, how do I make sure that I’ve got code that works constantly? Especially when I start dealing with more complex behaviors. I want to “record” my REPL sessions while focusing on building contexts since there are so many ways to build up a player and rooms. To do that, I’ve built a BDD micro framework based on E-Unit and a dash of rspec. I call it “test.hrl” and it is all of three macros!

-include_lib("eunit/include/eunit.hrl").
-define(It(Text,Func), {"It " ++ Text, Func}).
-define(It(Text,Setup,Cleanup,Func),
        {"It " ++ Text, setup,Setup,Cleanup,Func}).
-define(Describe(Text,Tests),{"Describe " ++ Text, Tests}).

It is based on the fact e-unit can used nested test descriptions to run tests. I just wrote some macros around it to make it fit my preconceptions of what a bdd framework should be like using terms I like.

When used, it looks like this

-module(player_tests).
-include("tests.hrl").

player_test_() ->[
  ?Describe("Bad Password",
    [?It("should return an error",fun setup/0,fun cleanup/1,
          ?_test(begin ?assertEqual(error, player:login("Tony", "BassPassword"))end))
    ]),
  ?Describe("Good Password",
    [?It("should have a player proxy",fun setup/0,fun cleanup/1,
                    ?_test(begin Me = player:login("Tony", "Hello"),
                                 ?assertEqual({ok,"You aint got jack!"},
                                 Me:inventory())
                           end))
     ]),
  ?Describe("Room Interaction",
    [?It("should describe the lobby",fun setup/0, fun cleanup/1,
         ?_test(begin Me = player:login("Tony", "Hello"),
                      ?assertEqual({ok, "It's a lobby"},
                                   Me:look())
                end)),
     ?It("should move to the kitchen", fun setup/0, fun cleanup/1,
         ?_test(begin Me = player:login("Tony", "Hello"),
                      Me:move("north"),
                      ?assertEqual({ok, "It's a kitchen"},
                                   Me:look()) end))])
].

setup() ->
        % I don't know why, but I need the print
        % to make the kitchen test pass
        io:format(""),
        stubs:fake_rooms().
cleanup(_Pid) ->
        stubs:stop_fake_rooms(),
        true.

Here’s my test runner

-module(test_runner).
-export([run/0]).
-include_lib("eunit/include/eunit.hrl").

run() ->
        eunit:test([player_tests],[verbose]).

The output looks like this


erl -noshell -pa ebin -s mnesia start -s test_runner run -s init stop
======================== EUnit ========================
module 'player_tests'
  Describe Bad Password
    It should return an error
      player_tests:7: player_test_...ok
      [done in 0.016 s]
    [done in 0.016 s]
  Describe Good Password
    It should have a player proxy
      player_tests:11: player_test_...ok
      [done in 0.015 s]
    [done in 0.015 s]
  Describe Room Interaction
    It should describe the lobby
      player_tests:18: player_test_...ok
      [done in 0.016 s]
    It should move to the kitchen
      player_tests:23: player_test_...ok
      [done in 0.016 s]
    [done in 0.032 s]
  [done in 0.063 s]
=======================================================
  All 4 tests passed.

This may not be perfect. I may not be the most ergonomic. I know it isn’t. But it’s close enough for me right now. I feel like I can describe what I want in a manner that fits me. Three macros and a slight change in how I view the world means I finally have this project moving forward well. It is truly amazing what just a little code can do.

No Comments

Parameterized Modules in Erlang

work safe

There is an erlang syntax that is beloved by OO converts to the language,  the parametrized module.  Here is why.

> Obj = param_example:new("Tony").
> Obj:name().
"Tony"

This Syntax makes an OO guy feel cozy.  And taking a look at the implementation of our module, we see the the implied variable is defined on the module definition.

-module(param_example, [Name]).
-export([get_name/0, set_name/1]).

get_name() -> Name.

There is a caveat.  If a module is parametrized, all functions are parametrized.  You cannot use non-parametrized functions to wrap your parametrized function, like this.

> Obj = param_example:new("Tony").
> ListOfNames = param_example:new_from_list(["Tom", "Dick", "Harry"]).

In fact, there’s some magic, functions actually have an arity of one more than you think. Actually, an extra parameter for each module parameter. In addition, these are constant, like all erlang variables. Since you don’t have access to a recursive call stack, you can’t change them.

However you can use a separate module to front- end the parametrized calls. And “mutating” calls can return new instances with new parameters.

So when Is it a good thing and when do you not want to use it?  I like the idea of using parametrized devices to wrap a bunch of data I’m going to be passing around but still has some functionality that needs to be called.

The scenario I didn’t find it useful for is having several processes that I can call without having a reference.  More concretely, I need to have rooms addressable via names of some other soft reference.  And their variables need to be modifiable. I abused named gen_server services for this.

-module(named_example).
-behaviour(gen_server).
-export([start_link/2, get_description/1, handle_call/3,init/1]).
-record(room, {name, description}).

server_name(Name) -> list_to_atom( lists:concat([ ?MODULE, "_", Name])).

start_link(Name, Description) ->
  gen_server:start_link({local, server_name(Name)}, ?MODULE, #room{name=Name, description=Description}, []).

get_description(Name) ->
  get_description:call(server_name(Name), description.

handle_call(description, _From, State) ->
  {reply, State#room.description, State}.

So, there are two ways of dealing with a collection of processes that are cleaner (don’t use the raw message send) than just passing around a Pid.  They do different things, but might could be used in combination.  I’m just not sure how, yet.

Oh, hey. If you have a better solution, please post a comment.

1 Comment

Project: MUD

work safe

I kind of mentioned this project at the tail end of the big py country post. I’ve been spending some time writing a rudimentary MUD in Erlang. Why? MUDs are fun. I want to know if Erlang is a fit for any future SEP projects. And I’m interested in what it would take to do the same thing on the .NET platform.

So, what do I mean by ‘rudimentary’ MUD? I want to connect via telnet. I want to issue commands. I want to move from room to room. I want to create rooms. I want to create items. I want to meet others waking around the mud. I want to run my mud across a cluster of servers. But, at this point scripting and combat aren’t in scope.

While I’ve made progress, Holidays happen and it will probably take a few weeks longer than the one week I was originally planning. I’ve got my telnet and command parser. I’m logging in and storing my users in the world. And that’s where I am right now.  It’s not just to holidays, it’s also learning a new language and it’s libraries.  I’ve had to fight with the dictionary for a while, then there’s some missing commas that keep cropping up.

I’m still trying to figure out the finer points of OTP, which may cause a rewrite of my telnet service.

Once I’m done, I’ll be putting together a more complete post on the project and start using this as a referance for a .NET implementation. Now, the .NET implementation may only be a roadmap and a short talk comparing the two platforms. Either way, this should be fun.

No Comments