Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

The Beauty of Perl 6 Parameter Passing
by Phil Crow | Pages: 1, 2

Assigning Defaults

Adding genuine, compiler-enforced parameters to sub declarations is a giant leap forward for Perl. For many people, that particular looseness in Perl 5 keeps it out of any discussions about what language to use for a project. I experienced this unfortunate reality firsthand in my last job. There's a lot more to declarations in Perl 6, though.

Suppose I want to give the caller control over the accuracy of the method, yet I want to provide a sensible default if that caller doesn't want to think of a good one. I might write:

    package Newton;
    use v6;

    sub newton(
        Num  $target,
        Num  :$epsilon = 0.005,  # note the colon
        Bool :$verbose = 0,
    ) is export {
        my Num  $guess  = $target;

        while (abs( $guess**2 - $target ) > $epsilon ) {
            $guess += ( $target - $guess**2 ) / ( 2 * $guess );
                    say $guess if $verbose;
        }

        return $guess;
    }

Here I've introduced two new optional parameters: $verbose, for whether to print at each step (the default is to keep quiet) and $epsilon, the Greek letter we math types often use for tolerances.

While the caller might use this exactly as before, she now has options. She might say:

    my $answer = newton(165, verbose => 1, epsilon => .00005);

This gives extra accuracy and prints the values at each iteration (which prints the value of the last iteration twice: once in the loop and again in the driving script). Note that the named parameters may appear in any order.

Making Assumptions

Finally, Newton's method can find roots for more things than just squares. To make this general requires a bit more work and some extra math (which I'll again brush under the rug).

It is easy enough to supply the function for which you want roots. For example, the squaring function could be:

        sub f(Num $x) { $x**2 }

Then, in the update line of the loop, write:

    $guess += ( $target - f($guess) ) / ( 2 * $guess );

Changing f would change the roots you seek.

The problem is on the far side of the division symbol. 2 * $guess depends on the function (it's the first derivative, for those who care). I could require the caller to provide this, as in:

        sub fprime(Num $x) { 2 * $x }

Then the update would be:

    $guess += ( $target - f($guess) ) / fprime($guess);

There are two problems with this approach. First, you need a way for the caller to pass those functions into the sub. That's actually pretty easy; just add parameters of type Code to the list:

    sub newton(
        Num  $target,
        Code $f,
        Code $fprime,
        Num  :$epsilon = 0.005,
        Bool :$verbose = 0,
    ) is export {

The second problem is that the caller may not know how to calculate $fprime. Perhaps I should make calculus a prerequisite for using the module, but that just might scare away a few potential users. I want to provide a default, but the default depends on what the function is. If I knew what $f was, I could estimate $fprime for users.

Perl 6 provides precisely this ability. Here's the final module, a bit at a time:

    package Newton;

    use v6;

That's nothing new.

    sub approxfprime(Code $f, Num $x) {
        my Num $delta = 0.1;
        return ($f($x + $delta) - $f($x - $delta))/(2 * $delta);
    }

For those who care (surely at least one person does), this is a second-order centered difference. For those who don't, its an approximation suitable for use in the newton sub. It takes a function and a number and returns an estimate of the value needed for division.

    sub newton(
        Num  $target,
        Code $f,
        Code :$fprime         = &approxfprime.assuming( f => $f ),
        Num  :$epsilon        = 0.0005,
        Bool :$verbose        = 0,
    ) returns Num is export {
        my Num $guess  = $target / 2;

        while (abs($f($guess) - $target) > $epsilon) {

            $guess += ($target - $f($guess)) / $fprime($guess);

            say $guess if $verbose;
        }
        return $guess;
    }

A script using this program could be as simple as:

    #!/usr/bin/pugs

    use Newton;

    sub f(Num $x) { return $x**3 }

    say "{ newton(8, \&f, verbose => 1, epsilon => .00005) }";

Note that the caller must supply the function f. The one in the example is for cube roots.

If the caller provides the derivative as fprime, I use it. Otherwise, as in the example, I use approxfprime. Whereas a caller-supplied fprime would take one number and return another, approxfprime needs a number and a function. The function needed is the one the caller passed to newton. How do you pass it on? Currying—that is, supplying one or more of the parameters of a function once, then using the simplified version after that.

In Perl 6, you can obtain a reference to a sub by placing the sub sigil & in front of the function's name (providing it is in scope). To curry, add .assuming to the end of that and supply values for one or more arguments in parentheses. All of this is harder to talk about than to do:

    Code :$fprime         = &approxfprime.assuming( f => $f ),

This code means that the caller might supply a value. If this is the case, use it. Otherwise, use approxfprime with the caller's function in place of f.

Conclusion

Perl 6 calling conventions are extremely well designed. Not only do they allow compile-time parameter checking, they also allow named parameters with or without complex defaults, even including curried default functions. This is going to be very powerful. In fact, with Pugs, it already is.

There is a slightly more detailed version of the example from this article in the examples/algorithms/ directory of the Pugs distribution. It's called Newton.pm.

Disclaimer

As much as it pains me to say it, if you need heavy duty numerics, don't code in pure Perl. Rather, use FORTRAN, C, or Perl with PDL. And be careful. Numerics is full of unexpected gotchas, which lead to poor performance or outright incorrect results. Unfortunately, Newton's method, in the general case, is notoriously risky. When in doubt about numerics, do as I do and consult a professional in the field.