Hi, I'm Tony Arcieri. You may remember me from such software projects as Celluloid, Reia, and Cool.io...

Saturday, June 19, 2010

Reia: Everything Is An Object

I recently added support for immutable objects to Reia. Immutable objects work in a similar manner to objects in languages like Ruby, except once created they cannot be changed. You can set instance variables inside of the "initialize" method (the constructor), but once they've been set, they cannot be altered. If you want to make any changes, you'll have to create a new object.

Now I've gone one step further: all of Reia's core types are now immutable objects. This means they are defined in Reia using the same syntax as user-defined immutable objects. And since Reia looks a lot like Ruby, that means their implementation should be easy to understand for anyone who is familiar with Ruby. Neat, huh?

When I originally started working on Reia, I drank Erlang-creator Joe Armstrong's kool-aid about object oriented programming. I wanted to map OOP directly on to the Erlang asynchronous messaging model, and proceeded along that path. When you sent a message to an object, I wanted that message to be literal, not some hand-wavey concept which was implemented as little more than function calls.

However, this meant concurrency came into play whenever you wanted to encapsulate some particular piece of state into an object. And if the state of that object never changed, not only was this needlessly complex, it was a total waste! Furthermore, the core types behaved as if they were "objects" when really they weren't... they pretended to work in a similar manner, but they were special blessed immutable objects. People asked me if they could implement their own immutable objects, and sadly my answer was no.

Encapsulating immutable states has always been a pain point for Erlang. The canonical approach, Erlang records, are a goofy and oft reviled preprocessor construct with an unwieldy syntax. Later Erlang added an "unsupported" feature called paramaterized modules, which feel like half-assed immutable objects. There are very few fans of either of these features.

The typical OOP thinking is that objects provide a great tool for encapsulating state. So why do Erlang programmers have to use things like records or parameterized modules instead of objects? Let's look at Joe Armstrong's reasoning:

Consider "time". In an OO language "time" has to be an object. But in a non OO language a "time" is a instance of a data type. For example, in Erlang there are lots of different varieties of time, these can be clearly and unambiguously specified using type declarations

Okay, great! So if we try to get the current time in Erlang, what does it give us?

Eshell V5.7.3 (abort with ^G)

1> erlang:now().

{1276,989504,651041}

Aiee! What the crap is that? In order to even begin to figure it out, we have to consult the "type declaration":

now() -> {MegaSecs, Secs, MicroSecs}

Okay, this is beginning to make more sense, after consulting the documentation. What we've received is a tuple, which splits the current time up into megaseconds, seconds, and microseconds since January 1st, 1970. Microseconds are split off so we don't lose precision by trying to store the value as a float, which makes sense. However, megaseconds and seconds were split up because at the time the erlang:now()function was written, Erlang did not have support for bignums. In other words, the type declaration is tainted with legacy.

So what if we have erlang:now() output, how do we, say, convert that into a human meaningful representation of the time, instead of the number of seconds since January 1st, 1970? Can you guess? Probably not...

1> calendar:now_to_local_time(erlang:now()).

{{2010,6,19},{17,34,40}}

Of course, the calendar module! I'm sure that's exactly where you were thinking of looking, right? No, not really. The response is decently comprehensible, if you know what time it is. However, this doesn't seem like a particularly good solution to me. I guess Joe Armstrong likes his functions and type declarations. I don't. So how does Reia do it?

>> Time()

=> #<Time 2010/6/19 17:36:36>

Look at that. Time is an object! An immutable object in this case. Thanks to the fact that there are functions bound to the time object's identity, it automatically knows how to display itself in a human-meaningful manner. Because the identity of this particular piece of state is coupled to functions which automatically know how to act on it, we don't have to do a lot of digging to figure out how to make it human meaningful. It just happens automatically.

In the end, I'm a fan of objects and Joe Armstrong is a fan of traditional functional programming principles. They're both solutions to the same problem, but my argument is Erlang doesn't have a good solution to the problem of user-defined types and coupling the identity of states to corresponding functions designed to act on them. In the case of the latter, Joe Armstrong thinks it's a bad thing whereas I consider it a basic, essential feature of a programming language. As for the former, Erlang has given us records and parameterized modules, neither of which are a good solution.

I recently learned (courtesy the JRuby developers) that when Matz created Ruby, he took the core functions of Perl and mapped them all onto objects. Every single core function in Perl has a corresponding Ruby method which belongs to a particular class. I am now doing the same thing with Erlang, mapping all of the core functions Erlang provides onto Reia objects.

"these can be clearly and unambiguously specified using type declarations"

I think his reasoning is sound only if the type system is much more expressive (ie not just tuples & sugar).

In ghci:

Prelude Data.Time.Clock> getCurrentTime2010-06-20 21:56:36.344085 UTC

With the type taking the shape:

data UTCTime = UTCTime { utctDay :: Day utctDayTime :: DiffTime}

Even with haskell's less than desirable records it doesn't seem so off putting. In reality if you could provide the Erlang equivalent of a Show instance for your sugared up tuples it could look identical to your example.

Erlang suffers in this area, sure, but the way you've written it makes it sound as though OOP has a better solution for data representation in all cases.