Philip Ratzsch: April 2008 Archives

Those of you playing along at home may have gotten the impression that I kinda like this whole Erlang thing. Correct. Those of you playing along at home (and wayward surfers as well) from outside of Europe may be wondering why there's a bothersome 'u' lurking in the spelling of 'behaviour'. Erlang came from Ericsson (who denies the connection between 'erlang' and 'ERicsson LANGuage'. They claim it was named after a mathematician of the same name.) and Ericsson just so happens to reside in Scandanavia.

Linguists theorize that they are allowed to put the letter 'u' wherever they please as a trade-off for having to use that weird o-with-a-line-through-it letter. Or maybe they just want to show the word how much they love empty sets. Regardless, that being said let's hope into gen_server.

gen_server is a component of OTP, the Open Telecom Platform. OTP can be thought of as a sort of application framework for Erlang, much as Python has Pylons, Ruby has Rails, and Windows has Problems Crashing. OTP is hilariously powerful, and being the relative new-comer that I am, I've only just started to really grasp just how powerful it is. To quote Joe Armstrong, author of the fantastic book "Programming Erlang":

    The power of OTP comes from the fact that properties such as fault tolerance, scalability, dynamic-code upgrade, and so on, can be provided by the behavior itself. In other words, the writer of the callback does not have to worry about [those things] because this is provided by the behavior.

Okay Philip, what's a behaviour? A behaviour is just what it sounds like - a bucket containing all the common behaviors of a certain kind of system or platform.

In the great tradition of contrived examples, imagine a movie store that would only let you check out one movie at a time. The code for that might look something like what I've included below. Just like last time, first the code and then the explanation. My eternal thanks go out to Jan Lehnardt of the CouchDB project for his assistance, patience, and friendship. In the near future, he's going to be posting a plok entry on this same subject, so by all means go see if that's there. And give him money.

  -module(movie_store).
  -behaviour(gen_server).
 
  -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 
  -export([checkout/2, lookup/1, start_link/0]).
 
  start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  checkout(Customer, Movie) -> gen_server:call(?MODULE, {checkout, Customer, Movie}).
  lookup(Customer) -> gen_server:call(?MODULE, {lookup, Customer}).

  init([]) ->
       Tab = ets:new(?MODULE, []),
       {ok, Tab}.
 
  handle_call({checkout, Customer, Movie}, _From, Tab) ->
       Response = case ets:lookup(Tab, Customer) of
         [] -> 
               ets:insert(Tab, {Customer, Movie}),
               {ok, Movie};
         [{Customer, OtherMovie}] ->
               {already_checked_out, OtherMovie}
       end,
       {reply, Response, Tab};
 
  handle_call({lookup, Customer}, _From, Tab) ->
        Reply = case ets:lookup(Tab, Customer) of
               [{Customer, Movie}] ->
                   Movie;
               [] ->
                   none
        end,
        {reply, Reply, Tab}.
 
  handle_cast(_Msg, State) -> {noreply, State}.
  handle_info(_Msg, State) -> {noreply, State}.
  terminate(_Reason, _State) -> ok.
  code_change(_OldVersion, State, _Extra) -> {ok, State}.

Okay - it looks terrifying (which incidentally, is Erlang's super power) but it's not. The first line declares the module name (and 'movie_store.erl' should also be the filename) and is a standard part of every Erlang program.

-behaviour(gen_server).can be thought of a line that declares which 'template' we'll be using for this program. The gen_server behaviour mandates that we define the functions specified in the first set of exports.

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). are the functions that gen_server expects to find (and will loudly complain if they are absent). I'll delve into more detail on these in the next tutorial, as this is going to be a huge entry as it is. For now, know that all of these have to be defined, even if they do nothing. As you can see at the bottom of the code, handle_cast/2, handle_info/2, terminate/2, and code_change/3 all do precisely nothing.

-export([checkout/2, lookup/1,start_link/0]). makes the custom functions that the server uses available to the outside world. They are in a separate -export statement only for clarity and can be combined with the one above.

start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

The starting point for the program. This function as you can see, serves as a wrapper for the command that spins up the server. {local, ?MODULE} shows that the server will be run locally and will not be available to other Erlang nodes in the cluster (as opposed to global). ?MODULE is a stand-in for the name for the current program name and specifies what we'll use to reference the server. The second ?MODULE tells the server where to find the callbacks (in this case, the same file). The remaining options allow you to turn debugging on, log to a file, and a few other things not used in this example.

checkout(Customer, Movie) -> gen_server:call(?MODULE, {checkout, Customer, Movie}).
lookup(Customer) -> gen_server:call(?MODULE, {lookup, Customer}).

These lines tie in the custom functions and tell gen_server what to do when (in this case) movie_store:checkout(...) and movie_store:lookup(....) are called. ?MODULE indicates where the function is, and {some_name,Arg1, Arg2,...} gives the function name and the arguments to be passed.

  init([]) ->
       Tab = ets:new(?MODULE, []),
       {ok, Tab}.
creates an database table using ets, the built-in memory database and returns it. In a deployed program, this would probably be something a bit more...permanent. A key component of a gen_server program is the program state. In this example, you'll see Tab getting thrown around into everything as it contains the present state of the movie store (who has which movie checked out).

  handle_call({checkout, Customer, Movie}, _From, Tab) ->
       Response = case ets:lookup(Tab, Customer) of
         [] -> 
               ets:insert(Tab, {Customer, Movie}),
               {ok, Movie};
         [{Customer, OtherMovie}] ->
               {already_checked_out, OtherMovie}
       end,
       {reply, Response, Tab};
Remember the gen_server:call(....) statements a little bit ago? This is what gen_server calls. You might wonder how there can be two handle_call functions defined with the same arity (the number of arguments, represented by the /x as in "functioname/2"). Look at the first argument, the three element tuple. Now look again at

checkout(Customer, Movie) -> gen_server:call(?MODULE, {checkout, Customer, Movie}).

The first elements match, which is how gen_server knows which handle_call to call to handle...the..call. Waay too many 'handle's and 'call's in that last bit. The important thing is that, like a lot of other things in Erlang, is based on pattern matching.

Notice that the table, Tab, has also been passed in. We go right into a case/of block that checks the table for the customer to see if they already have a movie checked out. If nothing is found ([] ->...) the movie is checked out to them and {ok, moviename} is returned by the block. If they already have a movie checked out, {already_checked_out, moviename} is returned by the block and they are not allowed to check out another movie (note: for this example, there is no way for a customer to return a movie. That is left as an exercise for the reader.).

In either case, the block's return value is stuffed into a tuple and shot back at whoever called the function. Before moving on to the next handle_call(...), notice the last line: {reply, Response, Tab};. You'd probably expect a period and not a semi-colon. The clauses of the handle_call(...) block are semi-colon delimited and will not work if they are terminated like other functions. I found that one out the hard way.

  handle_call({lookup, Customer}, _From, Tab) ->
        Reply = case ets:lookup(Tab, Customer) of
               [{Customer, Movie}] ->
                   Movie;
               [] ->
                   none
        end,
        {reply, Reply, Tab}.
This handle_call/3 defines a function to check and see if someone has a movie checked out. If so, it returns a tuple containing the customer's name and the movie title. Otherwise, the word 'none' is returned. I'm not going to spend a lot of time on this since it's so similar to the handle_call/3 we just looked at.

  handle_cast(_Msg, State) -> {noreply, State}.
  handle_info(_Msg, State) -> {noreply, State}.
  terminate(_Reason, _State) -> ok.
  code_change(_OldVersion, State, _Extra) -> {ok, State}.
The block of functions that need to be there for gen_server to work, but we're not using this time around. I'll cover these in a later tutorial. A quick note though - code_change/3 is really, really cool. It allows the server's code to be hot-swapped while the server is running. After all, why bother bringing a server down just because the code is changing, right?

Finally, here's how to run it:
2> c(movie_store).
{ok,movie_store}
3> movie_store:start_link().
{ok,<0.42.0>}
4> movie_store:checkout(phil, "Sneakers").
{ok,"Sneakers"}
5> movie_store:lookup(phil).
"Sneakers"
6> movie_store:checkout(phil, "Koyaanisqatsi").
{already_checked_out, "Sneakers"}
7> movie_store:lookup(phil).
"Sneakers"

One problem is that the program doesn't compliment you on your choice of movie if you try to check out 'Koyaanisqatsi'. Well, I've been long-winded enough for one day. I hope this was at the very least, interesting for you. If you improve on this, change it around, or whatever, by all means let me know.

Neon : Take Three

|

Alright - it turns out the RocketRAID is both a snare and a delusion.


  • The claims of hardware RAID are simply false - all processing is pawned off onto the system CPU.

  • The 'open-source drivers' are actually open-source wrappers for closed-source drivers. In either event, they're impossible to slip stream during OS installation.

  • Supports every major linux distro my ass.


So.....we've since purchased an LSI MegaRAID 150-6 RAID controller. I know for a fact this is a hardware RAID card. It also has a nifty little battery backup so it can finish writes in the event of a power failure. Since that battery can't possibly power the connected drives, I'm guessing that it just writes the data to on-board flash memory or something and completes the writes the next time the device is powered on. Regardless, it's a nice piece of equipment.

Now of course the OS install disk we're using is informing us that we don't have a valid CD drive attached. Keep in mind that this is coming from a program loaded off a CD. A problem for tomorrow.

On May 9th, I'm leaving the IS department at Rackspace. I like doing dev work on my own, but there is not the place to do it for me. I'm a single Ruby coder surrounded by Python guys and the end result is that I just have nothing to do. I'm looking for something internally, so we'll see how that goes.

Neon : Take Two

|

It seems that 64-bit Heron has some trouble recognizing RAID arrays. The machine booted and the RAID controller's configuration screen came up. After tweaking the necessary settings, we bounced the box and prayed that Heron would see it - which it didn't.

When we got to the installation section where we were going to do the partitioning, we were given prompted to choose between the twin 250GB Western Digital drives. Thinking that perhaps, somehow, someway the installed OS would see it we continued through the installation only to hit repeated checksum verification errors during the base system install.

Desperately hoping that this was a fluke, we shut off the machine, coated ourselves in honey, and sacrificed our entire apartment complex to the gods of data integrity and MD5 hashing. No luck (which means the install disk was probably corrupt - I'm having a chat with my LightScribe drive once I finish here). Once the honey had been removed and the police had left, we decided that we'd give Fedora a shot instead - the RAID controller specifically says that it plays nicely with it.

So that's where we stand now - even though Fedora 9 is coming out in about two weeks, we can't wait that long. If there are any hardware issues, we have less than two weeks to find them and get the equipment returned. At least for the time being, a Fedora machine is being added to the rack. I used Fedora at the last place I worked and while I didn't have anything specific against it, I didn't feel particularly attached to it.

As long as we're at it, we might as well trade bash in for tcsh - it's about time I learned some C and from what I understand The C SHell is a good place to learn as a lot of the syntax is similar. I don't have any first had experience though so we'll see.

Switching gears, I've found that a good way to gain some basic day-to-day experience with a language is to use it for any shell scripting needs I have. Erlang being my most recent language of study, that's what I'm going to do. If you'd like to give it a shot, Erlang programs can be run outside of the erl VM by typing:


pratzsch@carbon:/home/pratzsch/shell$erl -compile timely_message.erl
pratzsch@carbon:/home/pratzsch/shell$erl -noshell -s timely_message message -s init stop
Excuse me, your forehead's on fire
pratzsch@carbon:/home/pratzsch/shell$

...while it works, I'll probably end up aliasing that set of commands minus the program name to a bash script (oh, the irony) so I don't have to type that novella every time I want to run an Erlang program from the command line.

Welcome to neon!

|

neon_inside_labeled.jpg

These are the insides of 'neon', (not quite fully assembled) the latest web server. This is the first machine I've ever had that has hardware RAID. The chip is an 3.0GHz Intel Core 2 Duo of the 45nm variety, also a first.

The motherboard supports both DDR2 and DDR3 RAM, but it was decided that we'd rather have 4GB of DDR2 than 2GB of DDR3. Naturally, having 4GB mandates a 64-bit OS. We figured we'd give Heron a shot and see how that goes.

Rerlang

| | Comments (2)

One thing I hate doing in Ruby (or any language for that matter) is setting up a web service or anything that requires a read/write socket. That's one of the reasons I like Erlang so much - the messaging takes a lot of the hassle out of the equation.

You build a module, register it (so you can address the module by name instead of PID), and send messages (see my tutorial a few entries ago). I've also become interested in the message bus idea. All concerned processes are tied into a communication circuit wherein they listen for messages addressed to them and send replies as appropriate.

Erlang offers fantastic stability (one project that involved 2 million lines of Erlang code achieved a nine nines reliability, which is absolutely unheard of) but is somewhat counter-intuitive when you're getting started. Ruby (or Python, PHP, etc) is very fast to write and generally makes sense but I find it's a pain in the ass to do network I/O. God help you people who do it in C or Java.

So I got to thinking that having an Erlang-powered 'spine' may be just the thing for a SOA. If one bites the bullet and writes an Erlang back-bone to handle the interconnectivity between machines and act as a central nerve fiber between interpreted language processes.

The hard part in my mind would be adding a smooth interface between Ruby and Erlang but once that's done it's trivial to hang another Erlang endpoint off the bus to do the talking.

I'm probably not describing this well and as such it may come out sounding really, really stupid. But I think it will at the very least be a good learning experience. The more I work with Erlang the more I like it and the more potential I see in it. If you haven't looked at it, I strongly suggest you at least give it a try. It can look very strange and will challenge some of the basic ways you think about coding, but it is very worth it.

man page humor

|

Taken from the man page for syslogd (yes, another wild Saturday night):


There are a number of methods of protecting a machine:

1. Implement kernel firewalling to limit which hosts or networks have access to the 514/UDP socket.

2. Logging can be directed to an isolated or non-root filesystem which, if filled, will not impair the machine.

3. The ext2 filesystem can be used which can be configured to limit a certain percentage of a filesystem to usage by root only. NOTE that
this will require syslogd to be run as a non-root process. ALSO NOTE that this will prevent usage of remote logging since syslogd will be
unable to bind to the 514/UDP socket.

4. Disabling inet domain sockets will limit risk to the local machine.

5. Use step 4 and if the problem persists and is not secondary to a rogue program/daemon, get a 3.5 ft (approx. 1 meter) length of sucker rod*
and have a chat with the user in question.

Sucker rod def. -- 3/4, 7/8 or 1in. hardened steel rod, male threaded on each end. Primary use in the oil industry in Western North Dakota
and other locations to pump 'suck' oil from oil wells. Secondary uses are for the construction of cattle feed lots and for dealing with
the occasional recalcitrant or belligerent individual.

Messaging in Erlang

|

Erlang is a language that has fascinated me for lo, these last few weeks. For the unfamiliar, it was developed to implement high-availability, distributed, fault-tolerant systems for the telecom world decades ago and in recent years has witnessed a resurgence as multi-core processors become the norm, rather than the exception.

At first, Erlang is bit daunting. Variables aren't, line endings vary by location, and recursion is common-place, among others. After a few hours of playing with the language however, these 'problems' turn into 'features' and you begin to see the benefits of the language.

A key component of Erlang are processes. Processes, unlike threads in other languages, have no shared environment or data. The only manner of communicating things between processes is through messaging. Processes listen for messages addressed to them and take appropriate action. I've been playing with inter-processes messaging and wanted to share a simple example. First, I'll list the code and then walk you through it. The code is based on the 'Getting Started' tutorial at http://www.erlang.org

The Code:

-module(messagetest).
-export([start/0, first/2, second/0]).

first(N, SecondPID) ->
  SecondPID ! {N + 1, self()},
  receive
    continue ->
      io:format("First Continuing... ~n", []),
      first(N + 1, SecondPID);
    stop ->
      io:format("First Received 'Stop' command.  Stopping.~n", []),
      SecondPID ! done
  end.

second() ->
  receive
    done ->
      io:format("Second Received 'done'~n", []);
    {N, FirstPID} ->
      case (N < 10) of
        false -> FirstPID ! stop;
        true -> FirstPID ! continue
      end,
      io:format("Second got ~p ~n", [N]),
      second()
  end.

start() ->
  SecondPID = spawn(messagetest, second, []),
  spawn(messagetest, first, [0, SecondPID]).

The Explanation:

The first two lines define the module name and give a directory of the functions that are externally callable along with their argument counts. The last three lines are where the fun begins. Save the code in a file named 'messagetest.erl' and from the Erlang interpreter, compile it by typing:

c(messagetest). 

Next, run the 'start' function by typing:

messagetest:start().

You'll see output similar to:

<0.71.0>Second got 1 

First Continuing... 
Second got 2 
First Continuing... 
Second got 3 
First Continuing... 
Second got 4 
First Continuing... 
Second got 5 
First Continuing... 
Second got 6 
First Continuing... 
Second got 7 
First Continuing... 
Second got 8 
First Continuing... 
Second got 9 
First Continuing... 
Second got 10
First Received 'Stop' command.  Stopping. 
Second Received 'done'

The <0.71.0> is the process id (not to be confused with a *nix PID) and will very likely be different on your machine. Let's look at the 'start' function.

start() ->
  SecondPID = spawn(messagetest, second, []),
  spawn(messagetest, first, [0, SecondPID])

The first line is the function definition. Simple enough. The second line executes the spawn command which kicks off an Erlang process that executes the function called 'second' in the 'messagetest' module. No parameters are passed ('[]'). The return value of spawn is the process ID of the started process.

The second line also spawns another Erlang process, passing it a value of 0 as well as the process ID of the process that was just spawned.

Ironically enough, we'll examine the function called 'second' first as it's a bit easier to understand, I think. 'second' consists entirely of a receive/end block who's job is to receive messages passed to the function. Inter-process messages are sent via:

process_id_of_recipient ! message

When a process receives a message, it is placed in a FIFO queue and the next message is examined when a receive/end is encountered. Message processing is very similar to a case/switch in other languages in that the message is compared against a list of expected values and when a match is found the corresponding code is evaluated. If no match is found, the next message is examined while keeping the first message in the queue. If the second doesn't match, the third is examined and so on until either a message matches at which point it is removed from the queue, or there are no more messages. In the case of the latter, the process blocks and waits for a message it knows how to process.

second() ->
  receive
    done ->
      io:format("Second Received 'done'~n", []);
    {N, FirstPID} ->
      case (N < 10) of
        false -> FirstPID ! stop;
        true -> FirstPID ! continue
      end,
      io:format("Second got ~p ~n", [N]),
      second()
  end.

Here we can see that the function is written to respond to either the word 'done' or a tuple matching a {x,y} pattern. In the case of the word 'done' for example, a notice is printed and the function ends.

In the event the second pattern matches the message, the value of 'N' is evaluated to see if it's less than 10. If it isn't, a 'stop' message is sent to whatever process has a PID of 'FirstPID' - I'll show where this comes from in a moment. If it is, the message 'continue' is sent instead.

In either case, the number it received in the variable 'N' is printed and the function calls itself to begin listening again. Now over to the 'first' function.

first(N, SecondPID) ->
  SecondPID ! {N + 1, self()},
  receive
    continue ->
      io:format("First Continuing... ~n", []),
      first(N + 1, SecondPID);
    stop ->
      io:format("First Received 'Stop' command.  Stopping.~n", []),
      SecondPID ! done
  end.

'first' expects two parameters, a number and a PID. As you recall from the 'start' function, this information was supplied by calling:

spawn(messagetest, first, [0, SecondPID]).

'first' uses:

SecondPID ! {N + 1, self()},
to send a message to the process with a PID of 'SecondPID' consisting of the number it received plus one and it's own PID. It then enters a receive/end loop to wait for a response from the 'second' function. As you remember, 'second' checks to see if this number is greater than 10 and sends either 'stop' or 'continue' accordingly. In this case, the number is 1 so 'second' tells 'first' to continue.

This matches the

continue ->
  io:format("First Continuing... ~n", []),
  first(N + 1, SecondPID);

block, so a message is printed and 'first' calls itself passing N + 1 and the PID of the 'second' function's process. This cycle repeats until 'first' tells 'second' it's reached the number 10.

At this point, 'second' send a message consisting of 'stop' back to 'first'.

stop ->
  io:format("First Received 'Stop' command.  Stopping.~n", []),
  SecondPID ! done

'first' processes the message and prints that it is shutting down. Finally, the message 'done' is sent back to 'second'. 'first' shuts itself down as it has nothing left to do.

done ->
  io:format("Second Received 'done'~n", []);

'second' receives the 'done' message from 'first', prints an acknowledgment, and decides it's going out for a beer or something so it shuts down. The program terminates.

There's a lot going on and it can take some time to wrap your head around it, but probably significantly less time than you might think. I find Erlang to be really enjoyable to write, although I think the error messages could use some work. The people who wrote Erlang apparently founded the 'Obscure Error Message College' (note: in the 1990s, it was renamed to 'The Javascript "Has No Properties" Academy').

I hope this was useful to someone and that you enjoy working in Erlang.

Ruby Vector extensions, DNS

|

Finally got all the zones configured properly and transferred to my DNS server. In celebration, here are some additions to the Vector class. Enjoy.


require 'matrix'

class Vector
 
  def include?(search_term)
    self.to_a.include?(search_term)
  end
   
  def to_float
    new_elements = []
    self.to_a.each do |element|
        element = element.to_f if element.class == Fixnum
        new_elements << element
      end
          
      Vector.elements(new_elements)
  end

  def pretty  
    counter = 0 
    self.map do |element|
      puts "[#{counter}] = #{element}"
      counter += 1
    end
  end

  def empty?
    return true if self.nil?  
    empty = true
    self.map do |element|
      if case(element)
           when Fixnum
             element == 0
           when nil
             true
           when String
             element.empty? or element == ' '
           when Float
             element == 0.0
           end
        next
      else
        empty = false
        break
      end
    end

    empty
  end

  def self.random(size, max_value = 50)
    elements = []
    (1..size).each do |i|
      elements << rand(max_value) + 1
    end

    Vector.elements(elements)
  end

end

Taken from http://whois.domaintools.com/wordpress.com


Server Type: nginx/0.6.29

So it appears that Wordpress, which gets about 4 million hits a day has switched to an Nginx front-end! I really wish that tomorrow someone at work would say that no high-traffic sites uses Nginx. Of course I'd settle for an ignorant quip about how no there are no enterprise-level uses for Ruby or Rails.

It appears that the project I was on at work which was killed has risen from the proverbial ashes and is once again active.

I've been having DNS troubles since yesterday. First the serials weren't getting updated, then a full zone transfer wouldn't complete without an error, and now I've FINALLY eliminated all the errors in the DNS side of things. Not that it's working, of course. Now the trouble is that the external IPs of two of my boxes have been switched. So while it's resolving the domain names to the correct IP, that IP is tied to the wrong box. I should have it fixed by morning.

Apparently, it's tantamount to heresy to propose to use Nginx for anything Ruby-related other than Rails. I've scoured the internet (read: a good half-hour in between bites of Spaghettios) and I can't find anything on the subject. So, it looks like I'm going to have to 'figger it out my own sef'.

I doubt if either one of my readers has any experience on this front, feel free to chime in at any time.

Failing that, Ezra, on the off-chance you're reading this, send help. And sandwiches.

I had great plans for work this evening, but the better part of my night went to helping Roomie.male track down the reason why our zones weren't propagating (we think we have it, but we've got to wait and see).

I'm going to spend some time tomorrow digging into 'revactor', a gem which "...is an application framework for Ruby which uses the Actor model to simplify the creation of high-performance network services". In my unending attempt to get as low-level as possible with this stuff, I've started reading about π calculus. Initial impressions are that things like "∈ R/apple ∑ ramalama-ding-dong ⊗ 5% ♣ that's what she said" roughly translate to "Introduction". Still looks pretty cool, though.

Take the red pill

| | Comments (1)
For the weekend, please enjoy these add-ons to the Matrix class. Now, I'm off to hack together a fix for Matrix#coerce.

require 'matrix'

class Matrix

  def []=(row,col,value)
    @rows[row][col] = value
  end

  def include?(search_term)
    @rows.each do |row|
      return true if row.include?(search_term)
    end

    false
  end

  def to_float
    @rows.each do |row|
      row.each_index do |index|
        value = row.at(index)  
        row[index] = value.to_f if value.kind_of? Fixnum  
      end
    end
  end

  def to_int
    @rows.each do |row|
      row.each_index do |index|
        value = row.at(index)  
        row[index] = value.to_i if value.kind_of? Float 
      end
    end
  end

end

Anti-objects

|

When I was first introduced to object-oriented programming techniques, the major difficulty I experienced was learning how to think in an OOP-y manner. At first, the idea of everything being an object was weird, and capturing the behavior in my code felt non-intuitive and awkward.

As I gained experience, I ran into the road-block that most people run into at some point - it's one thing to define the behavior of a system object in code (such as a DNS record or a user account) but real-life objects can be exponentially more difficult. When it comes down to having an object make some sort of decision you frequently end up bogging yourself down in code, getting frustrated, and eventually giving up.

This represents a fundamental problem in how we think about program and object structure. I had one of those eye-opening moments yesterday (much like when I discovered cheese) that makes you pull back and think about how you do everything for a second.

When you work with anti-objects, the logic is pushed out of the obvious location and typically distributed over a number of smaller 'background' objects.

The example I read about was Pacman. The Pacman object would be easy - it just responds to user's commands. The ghost is the hard part, since it has to chase Pacman around and make decisions about where is the best place to go. The logic for that can get unwieldy pretty quickly. Here's where it gets weird.

When Pacman moves over a floor tile object, it leaves behind a little 'Pacman scent' that is diffused over the neighboring tiles. The tiles that are walls block the scent. The logic in the ghost tells it to follow the smell.

You really should read the full paper by Alexander Repenning at the University of Colorado.

I can't figure this out for the life of me...

The code below is for a simple logger class I wrote to go along with a personal project. To avoid having to write a ton of code I overrode method_missing thereby allowing calls like "logger_instance.emergency('emergency error text')".

For whatever reason, when method_missing-based calls are made, nothing happens. The code doesn't hang or crash, it just seems to skip right over the logging code and return. No errors are thrown that I can see, and it's really starting to bother me. Ideas?

class Utility::Log

LOG_FILE = './operator_log.txt'

def initialize
end

def method_missing(level, *mesg)
log(mesg[0], level.capitalize)
end

def log(mesg, level = 'NOTICE')
_new
@logger.write(Time.now + "::#{level}::" + mesg + "\n")
_close
end

private

def _new
if File.file?(LOG_FILE)
@logger = File.open(LOG_FILE, 'a')
end
end

def _close
@logger.close
end

end


About this Archive

This page is a archive of recent entries written by Philip Ratzsch in April 2008.

Philip Ratzsch: March 2008 is the previous archive.

Philip Ratzsch: May 2008 is the next archive.

Find recent content on the main index or look in the archives to find all content.