The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Signature::Attribute::Checked - apply value constraint checks to subroutine parameters

SYNOPSIS

With Data::Checks:

use v5.26;
use Sublike::Extended;
use Signature::Attribute::Checked;
use Data::Checks qw( Num );

extended sub add ($x :Checked(Num), $y :Checked(Num)) {
   return $x + $y;
}

say add(10, 20);            # this is fine

say add("hello", "world");  # throws an exception

DESCRIPTION

This module provides a third-party subroutine parameter attribute via XS::Parse::Sublike, which declares that values passed to a subroutine must conform to a given constraint check.

WARNING The ability for sublike constructions to take third-party parameter attributes is still new and highly experimental, and subject to much API change in future. As a result, this module should be considered equally experimental. Core perl's parser does not permit parameters to take attributes. This ability must be requested specially; either by using Sublike::Extended, or perhaps enabled directly by some other sublike keyword using the XS::Parse::Sublike infrastructure.

Additionally, the behaviour provided by this module should be considered more of a work-in-progress stepping stone. Ideally, constraint syntax ought to be provided in a much more fundamental way by Perl itself, allowing it to be used on my lexicals, class fields, and other places as well as subroutine parameters. This module is more of a placeholder to allow some part of that behaviour to be specified for subroutine parameters, while not getting in the way of a more general, more powerful system being added in future.

PARAMETER ATTRIBUTES

:Checked

extended sub f($x :Checked(EXPRESSION)) { ... }

Declares that any value passed to the parameter at the time the subroutine is called must conform to the constraint checker specified by the expression. Attempts to pass a non-conforming value will throw an exception and the subroutine body will not be invoked. Currently only scalar parameters are supported.

At compiletime, the string given by EXPRESSION is eval()'ed in scalar context, and its result is stored as part of the subroutine's definition. The expression must yield a value usable by Data::Checks. Namely, one of:

  • Any of the constraint checkers provided by the Data::Checks module itself.

  • An object reference with a check method:

    $ok = $checkerobj->check( $value );
  • A plain string giving the name of a package with a check method:

    $ok = $checkerpkg->check( $value );

    If using a plain package name as a checker, be sure to quote package names so it will not upset use strict.

    extended sub xyz ($x :Checked('CheckerPackage')) { ... }

As this is the interface supported by Types::Standard, any constraint object provided by that module is already supported here as well.

use Types::Standard qw( Str Num );

extended sub ghi ($x :Checked(Str), $y :Checked(Num)) { ... }

At runtime, this constraint checker is used every time an attempt is made to call the function. The checker is used as the invocant for invoking a check method, and the value for the parameter is passed as an argument. If the method returns true, the call is allowed. If false, it is rejected with an exception and the function body is not invoked.

(For performance reasons, the check method is actually resolved into a function at compiletime when the :Checked attribute is applied, and this stored function is the one that is called at assignment time. If the method itself is replaced later by globref assignment or other trickery, this updated function will not be used.)

It is not specified what order these checks are performed in. In particular, if any parameter default expressions invoke any side-effects, it is unspecified whether such side-effects will happen if a value passed for a parameter fails its constraint check. Users should take care not to attempt to invoke any side-effects during such expressions.

Note further that these value checks are only performed once, at the time the subroutine is invoked. Code within the body of the subroutine can freely assign any other kind of value to the variable corresponding to a :Checked parameter without issue.

extended sub foo ($n :Checked(Num)) {
   $x = "seven";   # this is permitted
}

SEE ALSO

AUTHOR

Paul Evans <leonerd@leonerd.org.uk>