Exploring Perl 6: Signatures, Part 2

| 2 Comments | No TrackBacks

In Part 1 of our exploration of signatures in Perl 6, we explored how we could use Perl 6's powerful and flexible type system to constrain how named and positional arguments could be passed to subroutines and methods. We also covered how to use slurpy signatures to create variadic functions that can take arbitrary named and positional lists of arguments.

Perl 6's signature system offers a lot more, however. In this post we'll examine some of the more advanced features that make Perl 6's calling semantics so powerful.

Class constraints

In addition to declaring variables to hold named and positional parameters within a signature, you can use signatures to specify type constraints on the parameters passed into functions. The types you use can be any class name.

sub foo( Numeric $foo, Str $bar ) { 
    say "my string is $bar and my number is $foo" 
}

This signature requires us to pass in arguments of type Numeric and Str. But because Perl 6's builtin types are just classes, and because Numeric has several subtypes, we can pass any type of number and it will work:

foo( 42, "blah" );
foo( 42.99, "yoohoo" );
foo( 3+9i, "hellooooooo" );
# etc

Signatures work the same way for classes that we declare ourselves.

class Foo { 
    has $.prop is rw 
}

sub inspect-a-foo( Foo $my-foo ) { 
    say "this foo's property is " ~ $my-foo.prop 
}

my $f = Foo.new( prop => 42 );
inspect-a-foo( $f );
# this foo's property is 42

In this example, the subroutine inspect-a-foo will only accept something of type Foo, or a subclass of Foo.

More specificity with the where block

Perl 6 allows us to restrict subroutine arguments even further, by using where clauses in the signature. The where clause takes any code block which must return a true value in order for the type constraint to pass.

sub foo( Int $positive where { $positive > 0 } ) { 
    say "I'm reasonably certain that $positive is positive!"
}

sub bar{ Foo $foo where { $foo.prop.isa( Int ) and $foo.prop > 40 } ) { 
    say "the property of this Foo is an integer greater than 40"
}

Multiple where blocks can be specified to put constraints on as many arguments as you like.

sub quadrant2( Real $x where { $x < 0 }, Real $y where { $y > 0 } ) {
    say "at the point ($x, $y)
}
quadrant2( 1, 1 );
# Constraint type check failed for parameter '$x'
quadrant2( -1, -1 );
# Constraint type check failed for parameter '$y'
quadrant2( -1, 1 );
# at the point (-1, 1)

You can even combine those two constraints by using a single block to check $x < 0 and $y > 0, but then the single constraint would be bound to only one parameter, which leads to confusing error messages if it's actually the other parameter which is the problem.

Constraint blocks don't actually have to even be blocks. In fact, anything of class Callable will work. Therefore, you can easily offload constraint checking to utility functions which can be recycled for multiple different subroutines.

sub is-positive( Real $n ) { $n > 0 }
sub is-negative( Real $n ) { $n < 0 }
sub is-zero( Real $n ) { $n == 0 }

sub quadrant1( Real $x where is-positive( $x ), Real $y where is-positive( $y ) ) { ... }
sub quadrant2( Real $x where is-negative( $x ), Real $y where is-positive( $y ) ) { ... }
sub quadrant3( Real $x where is-negative( $x ), Real $y where is-negative( $y ) ) { ... }
sub quadrant4( Real $x where is-positive( $x ), Real $y where is-negative( $y ) ) { ... }
sub x-axis( Real $x, Real $y where is-zero( $y ) ) { ... }
sub y-axis( Real $x where is-zero( $x ), Real $y ) { ... }
sub origin( Real $x where is-zero( $x ), Real $y where is-zero( $y ) ) { ... }

Weapon of Choice

Return types

Every Perl 6 subroutine can also specify its own return type as part of the signature. This can be done explicitly with the returns keyword, but I prefer the shortcut --> operator within the signature itself. The following two subroutine declarations are equivalent:

sub are-they-equal( Str $foo, Str $bar ) returns Bool { 
    $foo eq $bar 
}

sub are-they-equal( Str $foo, Str $bar --> Bool ) { 
    $foo eq $bar 
}

Unsurprisingly, Perl 6 will throw an exception if a subroutine with a declared return type attempts to return the wrong kind of thing.

Introspection

Like Perl 5, Perl 6 subroutines are first-class objects. But Perl 6 brings a wealth of new introspection facilities, including the ability to interrogate subroutines for signature information. Every subroutine's signature is actually an object of class Signature. We can find out about the subroutine's arity and and return type. And we can even grab a list of the Parameter objects in the signature.

sub are-they-equal( Str $foo, Str $bar ) returns Bool { 
    $foo eq $bar 
}

say &are-they-equal.signature.arity;    # 2
say &are-they-equal.signature.returns;  # (Bool)

my @params = &are-they-equal.signature.params;
say @params[0].name;      # $foo
say @params[0].type;      # (Str)
say @params[0].sigil;     # $

It is an understatement to say that Perl 6's signature system is powerful and robust. Not only is it a desperately-needed improvement over Perl 5's lack of true signatures in the core language, it is vastly more feature-rich than the signature system of any programming language commonly used today.

In the next installment, we'll take a look at even more features of signatures, including parameter traits and special binding modes.

No TrackBacks

TrackBack URL: http://friedo.com/cgi-bin/mt/mt-tb.cgi/35

2 Comments

For the type specifiers, isn't there also a way to say that the argument needs to be defined? Something like :D:? And also a way to say the invocant needs to be defined? Would be good to cover those shortcuts, using them would help prevent some run-type checks.


There's also a pragma for saying "from here on all, all the parameters default to requiring definedness", but don't know what it is either off the top of my head, or if it is enforced in Rakudo* 2016.01

I found the relevant P6 Signature docs about constraining to defined. If you want to require a parameter to be defined, add ":D" after the type. Use ":U" to constrain to undefined, and ":_" to explicitly allow either.

Thus:

multi is-zero( Real:D $n ) { $n == 0 }
multi is-zero( Real:U $n ) { False }
say 'is 0 zero? ',is-zero(0); # True
say ' for Real? ',is-zero(Real); # False

And though this isn't yet documented where I could find it, you can set "all parameters/invocants need to be defined unless otherwise specified"

use invocant :D;
use parameters :D; #`( Means all methods declared after this cannot
  be used as class methods, they will require a defined object as
  the invocant, unless you declare the invocant with :U or :_     )

Leave a comment

About this Entry

This page contains a single entry by Mike Friedman published on February 5, 2016 7:08 PM.

Exploring Perl 6: Signatures, Part 1 was the previous entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories

  • Exploring Perl 6

Pages

Powered by Movable Type 5.14-en