20070525

Pointers About References

I believe I mentioned before about really wishing I had pointers in Ruby. In the implementation of each class that's able to be loaded from XML data, there are several "properties" that describe each piece of data that would be described within its XML node. The main XML loading function (called loadProperties(), actually) knows how to take input XML data and a property object (not the original class, but a property object in the class) and load the data. So the property object needs to have pointers to the original class, in order to set the actual data values in there.

Since Ruby doesn't exactly have a pointer or reference type built in, I instead passed in two Procs: a getter and a setter. Due to the nature of closures, these allowed me to reference a member method of the correct object. At the same time, you had to manually create these stupid cookie-cutter procs for each property. Down the line, when there may be 100 properties in an XML readable class, that's a lot of extra, stupid code. There had to be a better way.

Turns out someone already wrote a pretty good Reference library for Ruby that does exactly what I want. It's a "gem," so I had to go install the whole RubyGems thing, but that's fine. The reference library, in its easiest use, takes symbols and turns them into references. Here's how it cut down the code I had before.

"Before" code.

"After" code:

@props << PersistedProperty.new(
              'short_description',
              ref(:shortDescription),
              String,
              protoRef(:shortDescription))

As you can see in the bolded portion, all I have to do is pass in the name of the accessor method in my current object, and it takes care of creating the "reference" for me. The magic behind the scenes for why calling this special ref method works is that ref is mixed in to Object. I'll leave the rest of that train of thought as an exercise to the reader.

The protoRef method creates a similar kind of reference, except to a property of this object's prototype (only applicable in classes that are prototyped, of course). I wrote that one myself. In any case, it cuts down my code significantly. I feel a lot better about the conciseness of this code.

lach:~/prog/ruby/mud/trunk$ svn log -r HEAD
------------------------------------------------------------------------
r151 | k | 2007-05-25 09:23:30 -0400 (Fri, 25 May 2007) | 6 lines

- give a more concise way of passing the procs in to PersistedProperty
- using Reference class
- use ref(:sym) to point to the method sym() in the current class
- use protoRef(:sym) to point to proto().sym()
- pass PersistedProperty.UseNewTypeFallback for procGetFallback to use prop.type.new() as the fallback
------------------------------------------------------------------------

20070517

Mud.getState()

Someone (Kirill) told me today I should post occasionally about the overall status of the MUD, as in, what features are in, what's blatantly missing, and such. What kind of game is it right now, and what level of maturity has it reached? I don't know a good format for it, but I do love lists...

Logging in
I know it sounds pretty basic, but you can log in to the MUD using telnet. Once in, you can type commands and things happen!
Multiple users at once
Another basic sounding thing. More than one person can be logged on to the MUD at the same time. Making this work turned the MUD into a multi-threaded server, with all the concurrency problems that come along with it. I probably have some race conditions and deadlocks in my code.
Linkdeadedness
Players who go linkdead (sever TCP connection without actually logging out of the MUD) will show up as linkdead, and when they reconnect, they will be attached to the already logged-in character. Making this work increased the flexibility of the networking stack and a few layers above it.
Communication commands
Players can communicate with each other using global chats (the ooc command, seen by everyone logged on) and in-room chat (the say command).
Movement
Players can walk from one room to another. There are only a few rooms, but the proof of concept of moving is demonstrated.
Looking
Players can look at what's in a room, look at a given person, mob, or item. They can look inside a container and see what's in it. They can look in their inventories and see what items they're carrying.
Object (dealie) manipulation
Players can pick up, drop, and destroy items. Players can get items from inside of containers. Players can pick up items that others have dropped.
Killing mobs
Players can slay mobs on the spot. This is only interesting because the MUD has to remove the mob from the game world.
Snooping / controlling

Players can snoop other players. Basically, they see everything that the other player sees, including the commands they type in exactly as they type them.

More interestingly, a player can snoop/control a mob. Once attached, the player sees from the mob's eyes and any commands he types in instead control the mob. It's a neat way for players to use NPCs to interact with other players, for role playing purposes. In later stages, the snoop command will be reserved for privileged users (immortals).

Descriptions
Players can edit their own descriptions using a simple in-game text editor that offers commands like appending a line, deleting a line, etc..
Targeted commands
This is actually kind of cool. Players can target their commands at a variety of targets:
  • look cover
  • look "barn cover"
  • look 1.cov
  • look 2.cov
  • drop all
  • drop all.cov
  • get all.cov 2.sack
  • look 1.
Variable delay commands

There is a command queue for each player, into which all the commands they type go. Different commands have different delays. For example, the look command completes very quickly, but (arbitrarily) the jump command (which right now does nothing more than echo a message) takes approximately 4 seconds to complete.

When a long command is going on, if the user types more commands, they all wait until the currently executing command finishes. There's an exception here: something called a "fast" command, which can jump right to the head of the queue. Somewhat arbitrarily, the emote command (think: "/me runs in a circle") is "fast", so even while you're jumping, you can type an emote to describe how you're jumping.

If you have several commands in your queue, the ~ command clears your queue. So if you just typed in a whole ton of slow commands and you change your mind, just use ~, and after the currently executing command finishes, whatever you typed in after the ~ will be at the head of your queue. This is actually a pretty standard feature for MUDs, and a necessary one in my opinion.

Creation
A proof of concept creation system is in place. When a user types in a username that's not in the MUD's database of players, they're taken into a wizard-style creation, where they're asked a series of questions leading up to the creation of their new character. Right now it's quite skeletal, but it demonstrates the capability of forking off the login process to a creation process, the wizard-style questions, and the creation of a new character based on the answers to the questions.
Rebooting and such

The MUD can be shut down using the shutdown command. This saves all players to disk and cleanly boots all the network connections, shuts down the main loop, etc., before finally terminating the ruby interpreter.

The MUD can be rebooted using the coldboot command. This basically performs a shutdown followed automatically by starting the ruby interpreter the same way one would manually from the command line. It's fairly primitive.

The cool feature is that the MUD can be rebooted on the fly using the hotboot command. Network connections are not severed, but all MUD code and areas are reloaded. This can be used in probably 95% of the cases, takes only a few seconds, and users might not even notice that it took place.

The hotboot can't be used when making modifications to the hotboot code itself, to the format of some of the internal data of the hotboot system, or possibly changing the XML format of data files. In general, it is both fast and danger.

Loading and saving world data from/to files
All dealies, rooms, and mobs are loaded from XML files on disk. Also, changes made to a player, including aliases, inventory, and so on, are saved to files too.
Color!
Everyone loves color! Using a familiar method from other muds, users can enclose text almost anywhere with mini-tags like {r and {x to display text in color.
Logging
There's a few decent methods internally for logging comments. Comments are logged with the file, line number, and function that generated them, which is handy. There's a method for asserting and another method for printing a stack trace from a given point, to help track down bugs. Generally, though, it's pretty primitive.
Eventos
Messages and notifications of things are passed as objects to listeners, like the room you're currently in, which can in turn forward them if need be. This allows for communication using object data rather than strings, which then in turn can lead to better mobprogs.
Tests
Not really a feature per se, but very useful in development. There are a number of tests in place to help catch regressions if I make code changes.
Aliases
Users can create fairly complicated aliases that are persisted with their player. These aliases can even be chained.
Socials and Pmotes
There is a fairly involved parsing system in place to support socials and pmotes (which are kind of the same thing), in which the player can use various directives embedded in their text to interpolate targets' short descriptions, names, etc.., even going so far as to properly use the grammatical case.

If I make major modifications, I will add things to this list.

20070515

Persistence and Perseverance

You may have been wondering where I've been for the last month or so. You may not have been wondering, and that's the more likely case, and that's fine. I've been working on a single, large change. I'm using the term "single" here somewhat loosely, in the nature of a revisioning system. That is, I've made only one checkin in the last month or so.This particular change is one that's been the bane of my existence for much longer than that.

I will describe the nature of the problem the same way I described it to my friend Odie the other day. This was actually the catalyst that helped me finally get around to figuring it out and finishing it. The problem has to do with loading various parts of world data from persisted storage. Characters, mobs, dealies (objects), and rooms all live in data files rather than being hard-coded into the code. I've unofficially described an XML format that the area files are created in. Builders will be able to modify the files by hand, using some special tools, and probably through an On-Line Creation system (OLC) like most MUDs have. I chose XML because I prefer how it looks to YAML, which was also in the running, and because it represents most of the relationships between pieces of data in the MUD pretty concisely--but that's a discussion for another day.

There are several types of dealie that will be loaded from disk, but the most complicated at this stage is a container (bag, box, etc.), which can contain other dealies (including other containers). So I need a way to get container dealies out of files and into the MUD. Let me state the premises for this problem.

The Problem

  1. For ease of building, every dealie has a prototype defined in the data file. The prototype describes the default attributes for that dealie, like a template. Nevertheless, it's kind of an abstract thing. You can't find a dealie prototype in the actual game world.

    Here is a sample dealie prototype:

    <dealie_prototype order="1">
        <kind>food_proto</kind>
        <lid area_name='main' local_id='10'/>
        <keywords>
            <keyword order="0">barn</keyword>
            <keyword order="1">cover</keyword>
            <keyword order="2">roof</keyword>
        </keywords>
        <short_description>the cover for a barn</short_description>
        <long_description>The spare roof of a barn is here.</long_description>
        <nourishment>10</nourishment>
    </dealie_prototype>

    Note the LID (local ID) given above. This uniquely idenfies this kind of XML-readable thing througout the MUD. There cannot be another prototype with LID main.10 in the world or else it's an error in the area file.

  2. Anywhere a dealie is needed, it references its dealie prototype, and then overrides any properties it wants to change for that instance.

    Here is a dealie that does not override any properties of the prototype:

    <item order="1">
        <kind>food_dealie</kind>
        <proto_lid area_name="main" local_id="10"/>
    </item>

    Here is a dealie that does override some properties:

    <item order="2">
        <kind>food_dealie</kind>
        <proto_lid area_name="main" local_id="10"/>
        <keywords>
            <keyword order="0">surprise</keyword>
            <keyword order="1">super</keyword>
        </keywords>
        <short_description>a surprise!!!</short_description>
        <long_description>a surprise lies here</long_description>
    </item>

    Note the proto_lid element, which points to main.10. Anything not overridden in this place will fall back to its value from the prototype. It's a convenient way to make minor modifications to dealies. Allowing individual dealies to be customized locally allows for things like restrings, which are nice to have.

  3. A prototype must have values for all of the properties of its type explicitly in the XML. That is, if a dealie does not override some property explicitly, it must be able to find a value for it in its prototype.

  4. There is a kind of dealie called a container dealie. It's not really a special kind, but it is a specific kind. It is able to contain other dealies inside it. Since a container dealie itself is a kind of dealie, container dealies can contain other container dealies.

  5. A container prototype can be defined with a set of default contents. Think, "a bag full of cheese." Here is a container prototype definition.

    <dealie_prototype order="5">
        <kind>container_proto</kind>
        <lid area_name='main' local_id='6'/>
        <keywords>
            <keyword order="0">pathological</keyword>
            <keyword order="1">sack</keyword>
        </keywords>
        <short_description>a pathological sack</short_description>
        <long_description>This sack is full of junk, and is the
            bane of all existence.</long_description>
        <max_num_dealies>5</max_num_dealies>
        <max_weight>10</max_weight>
    
        <default_contents>
            <kind>dealie_bag</kind>
    
            <item order="0">
                <kind>food_dealie</kind>
                <proto_lid area_name="main" local_id="1"/>
            </item>
            <item order="1">
                <kind>food_dealie</kind>
                <proto_lid area_name="main" local_id="10"/>
                <current_nourishment>1</current_nourishment>
            </item>
        </default_contents>
    </dealie_prototype>

    The default contents of this prototype are two food dealies. If this sack is referenced somewhere, it will by default have two food dealies inside.

  6. Since you can override attributes when instantiating dealies, you can likewise override the contents of a container when you instantiate a container dealie. Here is a container dealie that is overriding the prototype's default contents.

    <item order="0">
        <kind>container_dealie</kind>
        <proto_lid area_name="main" local_id="6"/>
        <contents_bag>
            <kind>dealie_bag</kind>
    
            <item order="0">
                <kind>food_dealie</kind>
                <proto_lid area_name="main" local_id="3"/>
            </item>
        </contents_bag>
    </item>
  7. In the previous point I made a container dealie that overrode its prototype to instead contain a single food dealie. But the following works (or should work) too:

    <dealie_prototype order="4">
        <kind>container_proto</kind>
        <lid area_name='main' local_id='7'/>
        <keywords>
            <keyword order="0">junky</keyword>
            <keyword order="1">sack</keyword>
        </keywords>
        <short_description>a junky sack</short_description>
        <long_description>This sack is full of tons of junk.</long_description>
        <max_num_dealies>5</max_num_dealies>
        <max_weight>10</max_weight>
    
        <default_contents>
            <kind>dealie_bag</kind>
    
            <item order="0">
                <kind>container_dealie</kind>
                <proto_lid area_name="main" local_id="7"/>
                <contents_bag>
                    <kind>dealie_bag</kind>
    
                    <item order="0">
                        <kind>food_dealie</kind>
                        <proto_lid area_name="main" local_id="3"/>
                    </item>
                </contents_bag>
            </item>
        </default_contents>
    </dealie_prototype>

    In this little gem, the prototype contains an instance of itself (albeit with an overridden contents). This scenario is the essence of the difficulty of what I've been working on. There's a cyclic nature to loading this data, coupled with ability to define things at different levels that puts the devil into the details. What solution would you use to tackle this?

  8. I should state formally that even though this is a somewhat cyclic problem, we're dealing with "real" items, so the cycle has to be terminated at some point. In the case above, finally the inner bag is overridden so that the container simply doesn't contain itself. That is illegal.

The Solution

Now I'll try to describe the solution I implemented to this problem (thank god it seems to be working now).

Each thing that needs to be loaded from persisted storage has attributed to it 0 or more properties.

Each property is a collection of several values of interest: the XML element name used to locate it within its parent node; a pointer/reference to where the actual data will be stored; the datatype of the property; and a pointer to a "fallback" location.

The fallback pointer is used for a dealie when it's NOT overriding a value. The fallback pointer in that case points to the corresponding value in its dealie prototype. The way to read it is: when setting this value, if the value is not defined explicitly in the XML, it is instead stored in the prototype.

  1. We start by loading up the full XML of all the areas defined. We go to every top-level prototype element in the XML and try to load the prototype (elaboration to follow). The attempt to load the prototype reports as to whether the prototype is fully loaded. When all prototypes are fully loaded, we're done loading prototypes.

  2. To load a single prototype, we first create a "skeleton" instance of it. Hereafter, we will fill in its properties as we can load them. So we iterate through its properties. The property has a pointer to where it should be loaded, so we can check whether the property has been set yet (null or non-null). If it hasn't been set, we try to load it.

  3. Properties are defined with the XML to locate it within the parent node, so we have enough information to figure out if the property is defined in the XML. If the element is not defined, we check the fallback. If the fallback is not defined either, we skip loading this property for now and come back to it in a later pass.

  4. If the property has been located, we can instantiate a new "skeleton" object for the property. Depending on what kind of datatype it is, we may be able to fully create it on the spot (numbers, strings, and other simple types).

  5. At this point, if we've been able to create an object for the property, we can use the property's pointer to the actual location to set it.

  6. Again depending on the data type, the object we just set may be fully created (simple types) or may have sub-properties that need to be loaded. In the latter case, we recursively call loadProperties for each of the object's sub properties.

  7. It's kind of handled as a special case, but after the first pass through, we expect to have all "simple" properties of the top-level prototypes loaded. At this point, we register them all in a global lookup by LID. Thereafter, when a dealie needs to find its prototype, it looks up the LID to find the (possibly only partially defined) prototype object.

  8. Since there has to be a termination to container cycles, we are guaranteed to make some progress every pass through. After some number of passes, eventually all the properties of all the objects will be loaded.

It's not efficient in the slightest, but it gets the job done. I may optimize it eventually, but since this loading phase happens before the network socket is even opened up, it doesn't affect anyone if it takes a performance hit. I'm worried, however, that with hundreds or thousands of items it may take on the order of minutes to start up the MUD. That's not really acceptable.

Some of you Ruby-savvy folks may have noticed my use of the word "pointer" above and wondered what I was talking about, since Ruby doesn't have pointers. Okay, you caught me. I didn't really use pointers. Conceptually what I needed was for an object to be able to store the location in another object that I could read from and write to. The way I implemented it is with a getter and setter method. Each property stores a proc that references something like @value to get the value, and another one that sets @value to something.

It's actually incredibly clunky to code with, and I really want to streamline it, since each property I create has to have up to three such methods defined (getter, setter, and fallback). Take a look:

procGetShortDescription = lambda {
    self.shortDescription
}
procSetShortDescription = lambda { |val|
    self.shortDescription = val
}
procFallShortDescription = lambda {
    self.proto() && self.proto().shortDescription()
}

@props << PersistedProperty.new(
              'short_description',
              procGetShortDescription,
              procSetShortDescription,
              String,
              procFallShortDescription)

Yuck! I really wish I had pointers right now. At one point I actually created a pointer type class. I've yet to decide if I'll actually use it. It's quite small. Here's its implementation.

def ref(sym)
    return Ref.new(lambda { |*val|
        if val.length > 0
            eval("#{sym.id2name} = val[0]")
        else
            eval(sym.id2name)
        end
    })  
end
        
class Ref
    def initialize(block)
        @block = block
    end
    
    def r()
        @block.call()
    end # function r

    def r=(val)
        @block.call(val)
    end # function r =
end

Other Miscellanea

Everything I talked about in here had to do with dealies, but mobs are equally important. I similarly added properties and such to mobs so that they can fit into the same algorithm. I probably won't be running into a cyclical problem with mobs anytime soon, though...

I have a lot of cleaning up to do with the code. There are a few things not represented using properties that really should be. For instance, room exits (connections between rooms) are currently handled separately, but ought to be able to be represented by properties easily enough.

I haven't done the work to allow for saving much to disk. The basic code is in place to save characters, but not inventories and other things that you'd want to save. I've toyed with the idea of a persistent world that never returns to some weird default state like most MUDs do. There are a lot of potential pitfalls there, so I'll need to consider the implications more before going off and typing some words.

Mostly, I'm just glad I finally got this basically solved. I had it solved badly for months and really wanted to get it out of the way so I could work on more interesting things... like mobprogs.

20070509

Ruby - Sparkles and Impurities

This is actually an old post that doesn't display properly on my old blog, but which I feel has relevant information for this one. It's also a precursor to a post I'll hopefully make this week or next about the progress I've been making in the last--oh, MONTH? I decided to repost this because I read another good blog post griping about Ruby.

In case you haven't read about it, I've been starting to code in Ruby. It's a fine language in its own right, and I'm having a good deal of fun using it. For ages I've been a serious Perl junkie. I've used it extensively at my last two jobs, and I totally dig it, because I love punctuation. It was sometime during last year that Caius and npj7y bugged me to learn it, so now I am. I read the fabulous (but badly indexed) Pragmatic Guide to Programming Ruby, written by the same jokers who wrote The Pragmatic Programmer (an excellent book to read if you aspire to programming).

Some parts of the language seemed fairly straightforward. Loops can be done as usual, conditionals (if-else) are normal, functions actually take a parameter list (as opposed to Perl, where they're both passed and returned through a variable called @_). I was pleased at first to see that Ruby is fully object oriented. Fully? As opposed to what? As opposed to Perl's hacked-in excuse for object oriented programming in a blatantly scripted language. Ruby's object orientation is not bad. It has a lot of features, but it also has what I think are its biggest flaws.

Ruby's concept of objects is related mostly to message-passing. When you want to invoke a member function of an object, say, make a bike shift gears, you send a message to the bike, saying, "switch to gear 5." In code-speak, myGaryFisher.changeGear(5). In strongly typed Object Oriented languages such as Java, C#, and C++, at compile-time, the compiler checks your source code to make sure that the object named myGaryFisher really does have a method called changeGear(). It won't let you run your program unless that's true.

More specifically, those languages attach a type to every object reference encountered in the program. So if you ever encounter the myGaryFisher object, it will actually be declared as Bicycle myGaryFisher a bit earlier (if anyone is thinking about polymorphism at this point, hang on; I'm getting there). This means that the compiler rigorously enforces not invoking methods on objects that it knows they do not have. If you try to call myGaryFisher.jointAround(), the compiler will check the type of myGaryFisher, see that it is of type Bicycle, then check the Bicycle class for a method called jointAround(). Of course since that method is not present in that class, the compiler will not produce machine code that tries to do that method invocation.

In Ruby, references are never declared with a type, so there is no type information checked at "compile time." Since Ruby is interpreted, this "compile time" would take place when the interpreter runs through the file and does a syntax check. Without the information that myGaryFisher is a Bicycle, all Ruby does is attempt to send the jointAround() message to the object and throw up its hands in disgust when it doesn't work. Though I believe that this kind of checking, done at compile time, can help eliminate buggy code, there's a more sinister issue behind it. After all, doing a typecast in Java always has the threat of a ClassCastException, which is the same kind of trouble.

The real trouble of this loose typing comes from the polymorphism features of the language. Here's a quick review for those of you who can't remember all those fancy OO words. myGaryFisher is a Bicycle, but if properly defined is also a Vehicle. In view of this, there are two ways in OO programming to consider the bicycle: as a bicycle, a two-wheeled thing with handlebars, a frame, foot pedals, derailleurs, a chain, gears, and everything that makes a bicycle what it is--all in all, a very specific view of the bike; or, as a vehicle, which has as its only real defining factors that it carries a certain number of passengers and that it is moving at some velocity.

In most (all?) OO languages, this is implemented via some kind of inheritance mechanism. In a strongly typed language, every time you see a reference to an object, the type determines what view of the object you have access to. If you get Bicycle myGaryFisher, because the compiler knows you have a Bicycle, you get access to a whole ton of descriptive functionality pertaining to a bicycle. If, instead, you get Vehicle myGenericPersonnelTransport, you instead have access only to a function that tells you how many people the thing can carry, and how fast it's going.

This is an information hiding tool; it allows you to give certain parts of the code access only to specific information that it is allowed to "know about." In a strongly typed language, the compiler checks this and generates code that conforms to these type restrictions. In Ruby, it does not. The only thing enforcing these type rules is you, and that, in my opinion, is just asking for trouble. When you call a function in some library, the only way you know what kind of object exactly the function is expecting is by reading the function's documentation... and we all know how keen programmers in general are about writing complete, up-to-date documentation (or can infer from the tone of this sentence).

I like the inheritance mechanism. It works well, and "mixins" are a good analog to interfaces (abstract base classes) found in Java, C#, and C++. Though I still have the benefit of code reuse through inheritance in Ruby, I lose the benefit of polymorphism. As far as I'm concerned, each of those makes up 50% of the value of inheritance. So I continue to make mixins and superclasses and things, but every once in a while I have to take a deep breath and do some type checking: this function is expecting an instance of this mixin; I must be very careful not to call any methods of the object, only methods of the mixin. I have to be cautious, because it's easy to forget, and the code will still work for the time being.

Moving on to other topics gripe-worthy, the standard library bothers me, because it appears to be hacked together quite randomly. It doesn't help that half the classes in there are lacking documentation. It doesn't help that some classes very much step on each others' toes.

For example, there is a Mutex class and also a Monitor class. The difference? The former does not allow a single thread to lock the mutex multiple times (why not?), but the latter does. The former is part of the Core API, and the latter is part of the standard libraries. I think they should be swapped.

I don't like that there isn't a Collection mixin or superclass or something, so that I can have a uniform way of adding and deleting elements from a Set, Array, and Hash.

I've always had a problem with operator overloading, and Ruby takes this to a whole new level. The | (pipe) operator for a number does a bitwise OR operation, but the same operator for an Array does a set union. Yes, these things are somewhat related, but doing this just seems like obfuscation, not convenience. Your goal should be to make code that's wrong look obviously wrong (this is one of the most brilliant programming articles I've read).

Similarly, Ruby's (and C#'s, for that matter) thing for "properties", that is, statements that look like you're setting a member variable of an object to a value, but actually are calling a function with the right-hand-side as a parameter,  bother me. I don't like deception like that. I want to know when I'm doing a simple assignment operation and when I'm calling a function. This is all about performance, isn't it? With the advent of harder, faster, stronger computers, this won't matter! Right? Wrong: what if the function that is secretly called has side effects in the object? Assignment is a relatively simple operation, and I want to know when something that I think is assignment actually is some O(n2) function.

Your response to this may be that I ought to open my eyes and read the docs, but the docs aren't there or are incomplete, half the time. I don't trust this kind of detail to documentation, only to enforcement by the language.

Despite all this, I think any of you programmers out there should learn some of this language. It allows you to do certain things very elegantly, which gives you insight about elegant ways to program in other languages you'll probably use more. Iterators and blocks (Procs), in particular, are very cool.

A Proc is basically a block of code that is stored as an object and can be passed around and used later. What's amazingly convenient about it is that the block can be returned from a function and still work. Normally, all the local variables would go out of scope once that function had returned, but a Proc always has access to all its local variables, even if those variables have gone out of scope.

I'm pleased that working with files and other I/O is just as simple in Ruby as it is in Perl. I am pleased that they have a fully function regular expression library that's integrated into the language (via /pattern/ type of expressions). I'm glad that hashtables have language support as well--for creating and accessing. It really is kind of nice that everything, including numbers, is an object. It saves me the time of considering whether I'm passing a primitive type or an object reference (Java, I'm looking at you).

Finally, I like two interesting naming conventions I've seen so far. For member functions that are queries, such as whether an object is equal to another, or whether some flag is set to true or not, the convention is to make use of a ? in the function name. For example, the equality test between two objects is Object.eql?(). Some member functions come in two flavors: one that does some processing on the object, but returns a new copy that reflects the changes; and one that directly modifies the object. For the latter, the convention is to append a ! on the function name. For example, the function that returns a new copy of a string with all the letters in losercase is String.downcase(), but one that modifies the string is String.downcase!(). It seems intuitive. The exclam alerts you that you're making a change--a potentially unsafe thing to do.

That's all for now. If I find anything else substantial, I guess I have to make a new post. Hope this was informative, and that you're all inspired to go out and try Ruby!