Sub::Contract module offers pragmatic contract programming for Perl.
SYNOPSIS
First of all, you should define a library of pseudo-type constraints. A type constraint is a subroutine that returns true if the argument if of the right type, and returns false or croaks if not. Example:
use Regexp::Common;
# test that variable is an integer
sub is_integer {
my $i = shift;
return 0 if (!defined $i);
return 0 if (ref $i ne "");
return 0 if ($i !~ /^$RE{num}{int}$/);
return 1;
}
sub is_shortdate { ... }
sub is_account_number { ... }
sub is_amount { ... }
# and so on...
To contract a function 'surface' that takes a list of 2 integers and returns 1 integer:
use Sub::Contract qw(contract);
contract('surface')
->in(&is_integer, &is_integer)
->out(&is_integer)
->enable;
sub surface {
# no need to validate arguments anymore!
# just implement the logic:
return $_[0] * $_[1];
}
Since the result of 'surface' is a function of its input arguments only, we may want to memoize (cache) it:
contract('surface')
->in(&is_integer, &is_integer)
->out(&is_integer)
->memoize
->enable;
If 'surface' took a hash of 2 integers instead, with the keys 'height' and 'width':
use Sub::Contract qw(contract);
contract('surface')
->in(height => &is_integer, width => &is_integer)
->out(&is_integer)
->enable;
sub surface {
my %args = @_;
return $args{height}* $args{width};
}
Of course, a real life example is more likly to look like:
use Sub::Contract qw(contract is_a);
# contract the method 'send_money' from class 'My::Account'
contract("send_money")
->in( is_a('My::Account'),
to => is_a('My::Account'),
amount => &is_integer,
date => &is_date )
->out( is_a('My::StatusCode') )
->enable;
# and a call to 'send_money' may look like:
my $account1 = new My::Account("you");
my $account2 = new My::Account("me");
$account1->send_money( to => $account2,
amount => 1000,
date => "2008-06-16" );
To make an argument of return value free of constraint, just set its constraint to undef:
contract("send_money")
->in( undef, # no need to check self
to => is_a('My::Account'),
amount => undef, # amount may be whatever
date => &is_date )
->enable;
You can also declare invariants, pre- and post-conditions as in usual contract programming implementations:
contract('foo')
->pre( &validate_state_before )
->post( &validate_state_after )
->invariant( &validate_state )
->enable;
To turn off all contracts within namespaces matching '^My::Account::.*$'
use Sub::Contract::Pool qw(get_contract_pool);
my $pool = get_contract_pool();
foreach my $contract ($pool->find_contracts_matching("My::Account::.*")) {
$contract->disable;
}
You may list contracts during runtime, modify them and recompile them dynamically, or just turn them off. See 'Sub::Contract::Pool' for details.
Product's homepage
Requirements:
· Perl