Thursday, July 14, 2011

Fuel Hook Methods

Mariano Martinez Peck explains that Fuel will get «hook» methods like StOMP has:
StOMP's hook methods are awesome and we want to have the same in Fuel. In fact, check the issue tracker and you will see several open issues regarding this :)

I think StOMP’s API for hook methods looks good, but the ability to (optionally) pass in an argument for the hook methods would be great. The argument passed would be (optionally) specified when invoking serialize / deserialize operations.

Why would you want to pass down an argument to be used in the hook methods? Well, not all serialize operations are simply about copying an object; sometimes you can use a serializer to copy only relevant parts of your model. Which parts you want to copy can vary with the context of the copy operation.

As an example, we have a large, complex, tree-like model containing hundreds of arrays with floats, where each position in the arrays represents the result of a Monte Carlo simulation. Some functions in the system require that we extract the results at a certain position from the arrays, or maybe a set of positions. Currently we implement the copy as variants of #copy* methods. This is hard to maintain, and error prone. If we could serialize and deserialize to create the customized copy, a lot of code could be removed.

Let’s say StOMP’s method Object stompWriteValue was called from a new method:

Object stompWriteValue: argument 
^self stompWriteValue

By default clients do not specify an argument. Object stompWriteValue: is called with nil as argument, and it calls #stompWriteValue. Now, if I wanted to pass an argument, I would override Object stompWriteValue: and use the argument to determine which write value to answer.

What you basically do is to pass down information about the context of the serialize operation. This context could be something simple as a symbol, or a more complex objects that you dispatch to.

I have not decided whether I really like this idea. :-) It might even be a bad idea: It complicates the API a bit, and complicates even more the internal implementation. Also, I like the idea to separate functions: A serializer should simply create a (binary) representation of an object. If you want to modify the copy, you should serialize the original, deserialize to a copy, and then modify the deserialized copy.

In one way you could argue that I want to tap into the serializer’s ability to traverse the object graph, visiting each object exactly one time, accepting circular structures. If I had access to these functions, having the argument in the hook methods might be less relevant. Such a function can be nice to have for a lot of operations.

But I am unsure how traversing should be implemented. How would a traverse function for example work when hitting #stompWriteValue (or its equivalent Fuel method)? It would not traverse the actual object, but risk getting a constructed object from that method.  Should the answer from this method be traversed? Or should a TraversingConstructedWriteValue exception be raised?

No comments: