Overview

Top Close Open

This design document proposes that TT3 will bless the hash array of named parameters that it passes to subroutines into a specific class (Template::Type::Params). This will allow the called subroutine to recognise the difference between named parameters provided by TT and any other reference to a hash array.

Description

Top Close Open

TT2 does some magical handling of arguments passed to a subroutine or method call. All named parameters are collected in a hash array and passed as the last argument.

[% foo(10, 20, x=50, 30, 40, y=60) %]
[% foo(10, 20, x=>50, 30, 40, y=>60) %]   # same as above in TT2

The foo() subroutine receives the arguments as if they were written:

[% foo(10, 20, 30, 40, { x => 50, y => 60 }) %]

This is discussed further in TT3 Language Design Document 3 - Arguments.

The problem is that the subroutine or method called has no way of differentiating between named parameters and a reference to any other hash array.

[% myhash = { x=10, y=20 };
   mycode(myhash);          # these three calls are all
   mycode(x=10, y=20);      # indistinguishable as far 
   mycode({ x=10, y=20 });  # as mycode is concerned
%]

This may not be a problem if your subroutine doesn't expect any hash references other than the named parameters. For example, you might write mycode to look for an optional hash reference passed as the last argument like this:

sub mycode {
    my $params = @_ && ref $_[-1] eq 'HASH' ? pop(@_) : { };
    my @args   = @_;
    # do something
}

However, if your subroutine is expecting a list of hash references and named parameters are optional, then the following will not work as expected.

[% mycode(hash1, hash2, hash3) %]   # assumes hash3 is named params

Whereas this will work just fine:

[% mycode(debug=1, hash1, hash2, hash3) %]

In this case, the presence of the debug named parameters causes TT to add the extra hash reference of named parameters to the arguments.

To solve this problem, TT3 will first bless any named parameters into the Template::Type::Params class.

# TT3
use constant PARAMS => 'Template::Type::Params';

sub mycode {
    my $params = @_ && ref $_[-1] eq PARAMS ? pop(@_) : { };
    my @args   = @_;
    # do something
}

The Template::Type::Params class is a direct subclass of Template::Type::Hash which implements the hash virtual methods. That means that you can call any of the hash virtual methods on the object.

print $params->join(' => ', "\n");
my $keys = $params->keys;
# etc

However, the object is also intended to be used transparently as a simple hash reference. Yes, it is an object, but only for the purposes of identification and to provide methods of convenience for the programmer. We're not worried about the encapsulation side of OO programming in this case so don't feel scared about digging right into the parameters. It really is just a "smart hash".

print $params->{ key1 };    # accessing hash items is officially OK

The Template::Type::Params module defines the PARAMS constant to save you from doing it (although you still have to load the module of course).

use Template::Type::Params 'PARAMS';

sub mycode {
    my $params = @_ && ref $_[-1] eq PARAMS ? pop(@_) : { };
    ...
}

The Template::Utils module defines the tt_args_opts() subroutine as a helper function to extract named parameters from the argument list.

use Template::Utils 'tt_args_opts';

sub mycode {
    my ($args, $opts) = tt_args_opt(@_);
    print "positional arguments are: ", join(', ', @$args), "\n";
    print "named parameters are: ", $opts->join, "\n";
}

Note that the $opts named parameters are a blessed hash reference, but the positional arguments in $args are a regular unblessed list reference.

(NOTE: this helper subroutine should probably be called tt_args_params() if we're going to be talking about "params". However there are some other places where I've used "opts" in the code base as the generic term for the optional named parameters. Might be confusing. Probably warrants revisiting and either clarifying why there are two names or picking one and sticking with it).

Rationale

Top Close Open

The TT2 parameter passing mechanism is broken. This fixes it.

Impact

Top Close Open

Any code currently testing for a HASH reference as the last argument to detect named parameters will need to be changed. The changes required would be minimal.


http://tt3.template-toolkit.org/docs/design/TT3DD04.html last modified 13:25:03 10-Dec-2009