[Templates] Fixing the hash.list virtual method

Andy Wardley abw@andywardley.com
Fri, 10 Oct 2003 10:07:03 +0100


A short time ago in a nearby thread, Simon Wilcox wrote:
> I have a routine that returns a hashref
> or an arrayref containing multiple hashrefs. If they're scalars then the
> .list vmethod automagically converts the single scalar into an arrayref
> but when they're hashref's, .list doesn't DWIM, instead it returns a list
> of hashrefs containing each key, value pair from the original hashref. I
> had to resort to the vmethod above and this code:

I think it's time to admit that this behaviour is wrong and change it.
Currently hash.list accepts an argument which is one of 'keys', 'values',
or 'each'.  But these are just duplicates for the hash.keys, hash.values
and hash.each vmethods so are effectively redundant.

   hash.list('keys')    # hash.keys
   hash.list('values')  # hash.values
   hash.list('each')    # hash.each

Currently, if you call hash.list without an argument then you get a 
list of hashrefs containing a 'key' and 'value'.  I can't think why I 
did this because I'm sure it's as good as useless if you're not prepared
for the data you're getting back.

However, that's not to say that the functionality isn't useful for when
you do know what's going on.  For example, you can use it to sort a hash
by value:

  [% FOREACH person IN people.list.sort('value') %]

So it's worth having this functionality, but I think it should be 
implemented as a separate vmethod.  Perhaps called 'kv' (for key/value),
'kvlist' or 'pairs', which is my current favourite (opinions and other 
suggestions welcome).

  [% FOREACH person IN people.pairs.sort('value') %]

hash.pairs would return what hash.list currently returns, a list of key/value
pairs, each implemented as a hash array with a 'key' and 'value' item.

  hash = {
    a = 'foo',
    b = 'bar',
    c = 'baz'
  }
  hash.pairs  # [ { key => 'a', value => 'foo' }
                  { key => 'b', value => 'bar' }
                  { key => 'c', value => 'baz' } ]

Of course you would get them in a non-deterministic order, but you 
get the idea.

Alternately, it could return a list of lists.

  hash.pairs  # [ [ a => 'foo' ]
                  [ b => 'bar' ]
                  [ c => 'baz' ] ]

Or we go the whole hog and have 2 different vmethods, hash.pairs to 
return a list of lists, and hash.kvlist to return a list of key/value 
hashes.  

Then it just remains to decide what hash.list should return.  Does it 
return a list containing the single hash reference? (i.e. it behaves
like scalar.list automatically promoting a single item to a list)

  hash.list   =>    [ { a => 'foo', ... } ]

Or does it behave like hash.each and return a list of the hash contents?

  hash.list   =>    [ a => 'foo', ... ]

Although it could be argued that the second is slightly more intuitive,
it's identical to hash.each.  So I think it should be the first - hash.list
returns a list containing the hash reference, as you have used it.

Either way, I think we should wait for TT3 before making this change
because it is likely to break templates that currently rely on that
behaviour.  We can go ahead and add the new vmethods now but leave
hash.list working as it currently does.  Additionally, we document the 
fact that hash.list will change at some point in the future and encourage
people relying on the existing behaviour to change their templates to 
use hash.keys, hash.values, hash.each, or hash.pairs (or hash.kvlist, etc).

To solve your immediate problem, we can add another option to the current 
hash.list vmethod, say 'list', which does what you want:

  hash.list('list')    # [ { a => 'foo', ... } ]

It's ugly, but it'll work for now until we can properly fix hash.list to
Do What It Should Have Done All Along (DWISHDAA).

How does that sound?

A