Closures to Ease Procedural Pain

Ovid on 2008-06-06T10:50:48

We have some legacy procedural code (xml builders) which really needs to be refactored into proper classes. This is a perfect case where classes are preferred to procedural code because we have several of these modules which have identical functions with identical arguments. They share a lot of behavior but have to pass their arguments everywhere. There are plenty of ad hoc functions to add XML elements, but I find myself routinely needing a simple, easy to use 'single element builder'. For example, if I just have <foo version="2">bar</foo>, I'm in a lot of pain because of how difficult it can be to build XML with this system. So the first thing I did was create this function:

sub add_simple_node {
    my ( $doc, $node, $name, $value, $attribs ) = @_;
    $attribs ||= {};

    my $element = $doc->createElement($name);
    $element->addChild($doc->createTextNode($value));

    foreach my $attrname (keys %{$attribs}) {
        my $attrvalue = $attribs->{$attrname};
        $element->setAttribute($attrname, $attrvalue);
    }
    $node->addChild($element);
}

To use it, I have to do something like the following. This is really ugly, but it's easier than doing this manually every time (the examples below don't show the optional attribute creation).

add_simple_node(
    $document,
    $segment_event_node,
    'offset',
    $segment_event->seconds_offset,
);
add_simple_node(
    $document,
    $segment_event_node,
    'position',
    $segment_event->position,
);
add_simple_node(
    $document,
    $segment_event_node,
    'title',
    $segment_event->title,
);

That might build and attach three simple elements like this:

200
2
first title

However, I still don't like that duplication. What I want is a proper instance where I can store the document and node, but I don't have that. Closures to the rescue!

sub make_node_builder {
    my ( $document, $node ) = @_;
    return sub { add_simple_node( $document, $node, @_ ) };
}

With that, my above code simplifies to the following much more readable code:

my $add_simple_node = make_node_builder( $document, $segment_event_node );

$add_simple_node( 'offset', $segment_event->seconds_offset );
foreach my $property (qw/position title/) {
    $add_simple_node->($property, $segment_event->$property);
}

If you find yourself wanting objects but you don't have classes, think about closures.