develooper Front page | perl.perl6.language | Postings from May 2004

Named parameters vs. slurpy hash syntax: brittle call syntax!

Thread Next
From:
Dov Wasserman
Date:
May 6, 2004 01:41
Subject:
Named parameters vs. slurpy hash syntax: brittle call syntax!
Message ID:
20040506023628.60840.qmail@onion.perl.org
[Reposting this from earlier this week, since it didn't seem to make it to
the news server. -dmw]

A question about the calling syntax of named parameters versus arguments
supplied to a slurpy hash. If I understand A6 and A12 correctly, all Pair
arguments to a function (sub, method, etc.) are scanned to see if their key
is a parameter name, and if so, its value is passed as the parameter's
argument. Otherwise, the Pair is bundled up into a slurpy hash if one is
defined; if no slurpy hash is defined and there are still pairs in the
argument list, it is a compile-time error. So far, so good.

My concern is that given an argument supplied as a pair (foo => "bar"), it
is not clear which type of parameter is meant to be supplied. If there is no
such named parameter, we know that the argument was intended for the slurpy
hash. However, what if the method designer later adds an optional parameter,
often to be implemented as a named-only parameter +$foo? (Or rather as ~$foo
if you agree with another post of mine ;-) Existing code providing the same
parameter name as a key for the slurpy hash may now break, since the target
(slurpy hash) was not clear.

An example may clear up the issue. Imagine we have:

sub logError($msg, *%errorInfo) {
    my Str $s = $msg;
    for %errorInfo.kv -> $k, $v {
        $s ~= "; $k=$v"; # append error info meta-data to error message
    }
    print "ERROR: $s.\n";
}

User code is now liberally sprinkled with log statements like:

1. logError("Username not found");
2. logError("Cannot login", module => "DB.pm", line => 68);
3. logError("Database error", module => "DB.pm", line => 263, prio =>
"HIGH");

Later on, the logging API is augmented to specify the logging priority
directly. Since we don't want to have to modify every existing log
statement, we add an optional named priority parameter, with a default:

sub logError($msg, Int +$prio = 4, *%errorInfo) {
    my Str $s = $msg;
    for %errorInfo.kv -> $k, $v {
        $s ~= "; $k=$v"; # append error info meta-data to error message
    }
    # translate numeric priority level [1-7] to
    # a String description like "ERROR", "ALERT", etc.
    my Str $prio_desc = getPrioDescription($prio);
    print "$prio_desc: $s.\n";
}

Most of the existing calls will work fine. But what about calls like #3
above:

logError("Database error", module => "DB.pm", line => 263, prio => "HIGH");

After the New And Improved logError() routine is rolled out, it seems to me
that this log statement should generate a compile-time error, since the
named Int parameter "prio" is given a non-integer argument "HIGH". At best,
this should be a run-time error. Either way, this confusion undermines a key
Perl strength: ability of the user to upgrade code (without breaking it).

I have thought a bit about possible fixes, and while there are many
possible, I think the best approach would be to separate the related but
distinct concepts of supplying an argument to a named parameter from the
general Perl 6 notion of a Pair.

Reusing the Pair creation operator '=>' for named parameter binding is a
nice use of orthogonality, but perhaps reminiscent of the too-orthogonal
object-oriented notation in Perl 5 (as Larry nicely laid out in A6). What we
really mean by

logError($err_msg, prio => 3);

is:

"bind the argument $err_msg to the first positional parameter in logError(),
and bind the value 3 to the parameter named "prio".

Yes, the parameter name and argument value form a pair of entities, but that
is not their essential characteristic.

To distinguish these two cases, what if we used the := binding operator to
bind an argument to a named parameter:

logError($err_msg, prio := 3);

AFAICT, the ":=" operator would currently be illegal in this context. This
opens it up to this new, but highly-related, meaning. It also means that a
caller can supply any number of Pair arguments, and they must map either to
positional Pair parameters, or to the slurpy hash. Otherwise, it is a
compile-time error.

And future compatibility is ensured, since only arguments passed via ':='
will get mapped to named arguments, and only arguments that look like "foo
=> $bar" can get mapped to explicit Pair parameters or implicitly to a hash.
If for some reason ':=' cannot work here, how about it's compile-time
brother '::='? (Yes, we try to comply with Larry's First Law of Language
Design: Everyone wants the colon ;-)

If neither one of these operators can be used syntactically, I'd still
suggest a distinct syntax for supplying these two distinct types of
parameters.

BTW, I understand that a slurpy hash is used in object construction via
bless and friends, but what is its usefulness in the rest of the world?
Whereas slurpy array *@data is meant to accept an arbitrary amount of
anonymous "factory-supplied" data, is *%hash just a sop for anonymous
meta-data? Shouldn't meta-data be explicitly declared in the signature so
that the meaning of each parameter is understood? If not, we have
ontological overload (i.e., I have received lots of meta-data about this
method call in my method body, but I don't really know what most of it means
or can be used for). I can see how this might be needed in generic object
construction code, but if there's not much use for it in general user-level
code, perhaps the slurpy hash is a feature in search of a purpose. It should
certainly be allowed, but not without more explicit and verbose syntax,
given the low Huffman need. Unless of course I am totally off here.

Feel free to let me know where I may have gone wrong. I'm sure that someone
will if I have ;-)

-Dov Wasserman
 dov@wass.ws



Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About