Design considerations

Ovid on 2005-10-15T20:42:03

I just released Array::AsHash 0.20. The version number has been jumping quickly due to rapid development of this code. This development has been driven by real, live needs. You can take a gander at it if you're curious to see what my production code is like though I admit there's a bit of duplication that I'm trying to figure the best way of factoring out. I'm actually not terribly worried about the duplication (yet). That's because I have a good test suite and what's more important is that I present a good interface. The internals can be dealt with once I know that my interface is solid.

This code has solved certainly problems for me quite handily and the current interface reflects this. Perhaps the most interesting bit is the following construct you'll see in some methods:

return wantarray  ? @results
  : @arg_list > 1 ? \@results
  :                 $results[0];

What the heck is that? Well, think about getting a value from a hash:

my $val = $hash{$key};
my $val = $array->get($key);

My get method (and others) returns a single value in scalar context. What about a hash slice though?

my @vals = @hash{qw/key1 key2 key3/};
my @vals = $array->get(qw/key1 key2 key3/);

In scalar context, we usually think of an array returning the number of elements it contains. However, a hash slice dos not return an array, it returns a list. This means that in scalar context, we'll get the value of the last item it returns.

my %hash  = qw/one un two deux three trois/;
my $count = @hash{qw/one three/};
print $count; # probably not what we wanted

That prints "trois". It is, in fact, pretty useless for most folks. Thus, I don't want my get method to behave this way. So what's a standard Perl idiom for returning an array from a method in scalar context? I return an array reference. Because I provide separate "count" methods, I don't think this is too much of a problem. I find the array reference to be more useful. However, that poses a problem:

my $value = $array->get('foo');

Because that's in scalar context, it should return an array reference. However, that doesn't behave the way most people would be expecting it to. You could force it to work correctly like this:

my ($value) = $array->get('foo');

That, however, is ugly and is going to confuse people. Thus, I have a rule for several methods. If called in scalar context with a single argument, they return a scalar. Mostly things work as expected though the following is problematic:

my $values = $array->get(@list);

If @list contains only one argument, $values will be a single value, not an array reference containing the values to be returned (though it might be an aref if that's what what stored in that slot). This is because Perl's argument handling is primitive enough that it's not easy to distinguish this case. This forces the programmer to do the following:

my $values = $array->get(@list);
$values = [$values] if 1 == @list;

That's ugly, but since this is not the common case, I chose not to optimize for it.

In other news, you can now optionally create "strict" hashes with this module. Use a key which doesn't exist and the module falls down and goes boom. I do provide explicit methods for using keys which don't exist, though; you just can't implicitly work with or ignore non-existent keys. You have to tell the object exactly what you want to do. See the docs for more details.

Other features of the new release of this module:

0.20    2005-10-15
        "strict' mode added.  Using non-existent keys is fatal.
        "strict" method also added.
        Reorganized the docs to be a bit cleaner.

0.12    Not released (these are all in 0.20, though)
        "put" can now accept an even-sized list of pairs.
        "get" can now take a list of keys.
        Did a bit of an internals cleanup.
        each() now returns an iterator in scalar context
            Thanks to Adrian Howard for the suggestion.
        Added "rename" method.
        Overloaded stringification as a debugging aid.