The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Sub::Contract - Pragmatic contract programming for Perl

SYNOPSIS

To contract a function 'divid' that accepts a hash of 2 integer values and returns a list of 2 integer values:

    contract('divid')
        ->in(a => sub { defined $_ && $_ =~ /^\d+$/},
             b => sub { defined $_ && $_ =~ /^\d+$/},
            )
        ->out(sub { defined $_ && $_ =~ /^\d+$/},
              sub { defined $_ && $_ =~ /^\d+$/},
             )
        ->enable;

    sub divid {
        my %args = @_;
        return ( int($args{a} / $args{b}), $args{a} % $args{b} );
    }

Or, if you have a function is_integer:

    contract('divid')
        ->in(a => \&is_integer,
             b => \&is_integer,
            )
        ->out(\&is_integer, \&is_integer);
        ->enable;

If divid was a method of an instance of 'Maths::Integer':

    contract('divid')
        ->in(sub { defined $_ && ref $_ eq 'Maths::Integer' },
             a => \&is_integer,
             b => \&is_integer,
            )
        ->out(\&is_integer, \&is_integer);
        ->enable;

Or if you don't want to do any check on the type of self:

    contract('divid')
         ->in(undef,
              a => \&is_integer,
              b => \&is_integer,
             )
        ->out(\&is_integer, \&is_integer);
        ->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;

You may memoize a function's results, using its contract:

    contract('foo')->memoize->enable;

You may list contracts during runtime, modify them and recompile them dynamically, or just turn them off. See 'Sub::Contract::Pool' for details.

DESCRIPTION

Sub::Contract offers a pragmatic way to implement parts of the programming by contract paradigm in Perl.

Sub::Contract is not a design-by-contract framework.

Perl is a weakly typed language in which variables have a dynamic content at runtime. A feature often wished for in such circumstances is a way to define constraints on a subroutine's arguments and on its return values. A constraint is basically a test that the specific argument has to pass otherwise we croak.

For example, a subroutine add() that takes 2 integers and return their sum could have constraints on both input arguments and on the return value stating that they must be defined and be integers or else we croak. That kind of tests is usually writen explicitely within the subroutine's body, hence leading to an overflow of argument validation code. With Sub::Contract you can move this code outside the subroutine body in a relatively simple and elegant way. The code for add and its contract could look like:

    contract('add')
        ->in(\&is_integer,\&is_integer)
        ->out(\&is_integer)
        ->enable;

    sub add { return $_[0]+$_[1] }

Sub::Contract doesn't aim at implementing all the properties of contract programming, but focuses on some that have proven handy in practice and tries to do it with a simple syntax.

With Sub::Contract you can specify a contract per subroutine (or method). A contract is a set of constraints on the subroutine's input arguments, its returned values, or on a state before and after being called. If one of these constraints gets broken at runtime, the contract fails and a runtime error (die or croak) is emitted.

Contracts generated by Sub::Contract are objects. Any contract can be disabled, modified, recompiled and re-enabled at runtime.

All new contracts are automatically added to a contract pool. The contract pool can be searched at runtime for contracts matching some conditions.

A compiled contract takes the form of an anonymous subroutine wrapped around the contracted subroutine. Since it is a very appropriate place to perform memoization of the contracted subroutine's result, contracts also offer memoizing as an option.

There may be only one contract per subroutine. To modify a subroutine's contract, you need to get the contract object for this subroutine and alter it. You can fetch the contract by querying the contract pool (see Sub::Contract::Pool).

The contract definition API is designed pragmatically. Experience shows that contracts in Perl are mostly used to enforce some form of argument type validation, hence compensating for Perl's lack of strong typing, or to replace some assertion code.

In some cases, one may want to enable contracts during development, but disable them in production to meet speed requirements (though this is not encouraged). That is easily done with the contract pool.

DISCUSSION

Definitions

To make things easier to describe, let's agree on the meaning of the following terms:

Contractor: The contractor is a subroutine whose pre- and post-call state and input and return arguments are verified against constraints defined in a contract.
Contract: Defines a set of constraints that a contractor has to conform with and eventually memoizes the contractor's results.
Constraint: A test that returns true when the constraint passes, or either returns false or croaks (dies) when the constraint fails. Constraints are specified inside the contract as code references to some test code.

Contracts as objects

Sub::Contract differs from traditional contract programming frameworks in that it implements contracts as objects that can be dynamically altered during runtime. The idea of altering a contract during runtime may seem to conflict with the definition of a contract, but it makes sense when considering that Perl being a dynamic language, all code can change its behaviour during runtime.

Furthermore, the availability of all contracts via the contract pool at runtime gives a powerfull self introspection mechanism.

Error messages

When a call to a contractor breaks the contract, the constraint code will return false or croak. If it returns false, Sub::Contract will emit an error looking as if the contractor croaked.

Contracts and context

In Perl, contractors are always called in a given context. It can be either scalar context, array context or no context (no return value expected).

How this affects a contractor's contract is rather tricky. The contractor's return values may be context sensitive. Therefore, the following choices were made when designing Sub::Contract:

  • If a subroutine usually returns a scalar or an array but is called in void context, the part of the contract that validates return values will not see any return values from the subroutine. This implies that calling in void context a subroutine whose contract has constraints on the return values will be seen as a contract breach.

  • If a subroutine returns an array but is called in scalar context, the part of the contract that validates return values will see only 1 return value from this subroutine: an integer telling the number of elements in the returned array. This implies that calling in scalar context a subroutine whose contract has constraints on a list of return values will be seen as a contract breach.

Issues with contract programming

Inheritance

Contracts do not behave well with inheritance, mostly because there is no standard way of inheriting the parent class's contracts. In Sub::Contract, child classes do not inherit contracts, but any call to a contractor subroutine belonging to the parent class from within the child class is verified against the parent's contract.

Relevant error messages

To be usable, contracts must be specific about what fails. Therefore it is prefered that constraints croak from within the contract and with a detailed error message, rather than just return false.

A failed constraint must cause an error that points to the line at which the contractor was called. This is the case if your constraints croak, but not if they die.

Other contract APIs in Perl

  • Sub::Contract VERSUS Class::Contract

    Class::Contract implements contract programming in a way that is more faithfull to the original contract programming syntax defined by Eiffel. It also enables design-by-contract, meaning that your classes are implemented inside the contract, rather than having class implementation and contract definition as 2 distinct code areas.

    Class::Contract does not provide memoization from within the contract.

  • Sub::Contract VERSUS Class::Agreement

    Class::Agreement offers the same functionality as Sub::Contract, though with a somewhat heavier syntax if you are only seeking to validate input arguements and return values.

    Class::Agreement does not provide memoization from within the contract.

    TODO: more description TODO: how to enable contracts -> enable on each contract, or via the pool TODO: validation code should not change @_, else weird bugs...

Object API

my $contract = new Sub::Contract($qualified_name)

Return an empty contract for the function named $qualified_name.

If $qualified_name is a function name without the package it is in, the function is assumed to be in the caller package.

    # contract on the subroutine 'foo' in the local package
    my $c = new Sub::Contract('foo');

    # contract on the subroutine 'foo' in the package 'Bar::Blob'
    my $c = new Sub::Contract('Bar::Blob::foo');

A given function can be contracted only once. If you want to modify a function's contract after having enabled the contract, you can't just call Sub::Contract-new> again. Instead you must retrieve the contract object for this function, modify it and enable it anew. Retrieving the function's contract object can be done by querying the contract pool (See 'Sub::Contract::Pool').

my $contract = new Contract::Sub($name, caller => $package)

Same as above, excepts that the contractor is the function $name located in package $package.

$contract->invariant($coderef)

Execute $coderef both before and after calling the contractor.

$coderef gets in arguments the arguments passed to the contractor, both when called before and after calling the contractor. $coderef should return 1 if the condition passes and 0 if it fails. $coderef may croak, in which case the error will look as if caused by the calling code. Do not die from $coderef, always use croak instead.

    package MyCircle;

    use accessors qw(pi);

    # define a contract on method perimeter that controls
    # that the object's attribute pi remains equal to 3.14
    # before and after calling ->perimeter()

    contract('perimeter')
        ->invariant(sub { croak "pi has changed" if ($_[0]->pi != 3.14) })
        ->enable;

    sub perimeter { ... }
$contract->pre($coderef)

Same as invariant but executes $coderef only before calling the contractor.

$coderef gets in arguments the arguments passed to the contractor. $coderef should return 1 if the condition passes and 0 if it fails. $coderef may croak, in which case the error will look as if caused by the calling code. Do not die from $coderef, always use croak instead.

$contract->post($coderef)

Similaar to pre but executes $coderef when returning from calling the contractor.

$coderef gets in arguments the return values from the contractor, eventually altered by the context (meaning () if called in void context, a scalar if called in scalar context and a list if called in array context). $coderef should return 1 if the condition passes and 0 if it fails. $coderef may croak, in which case the error will look as if caused by the calling code. Do not die from $coderef, always use croak instead.

$contract->in(@checks)

Validate each input argument of the contractor one by one.

@checks declares which validation functions should be called for each input argument. The syntax of @checks supports arguments passed in array-style, hash-style or a mix of both.

If the contractor expects a list of say 3 arguments, its contract's in should look like:

    contract('contractor')
        ->in(\&check_arg0, \&check_arg1, \&check_arg2)

Where check_argX is a code reference to a subroutine that takes the corresponding argument as input value and returns true if the argument is ok, and either returns false or croaks if the argument is not ok.

If some arguments need not to be checked, just replace the code ref of their corresponding constraint with undef:

    # check input argument 0 and 2, but not the middle one
    contract('contractor')
        ->in(\&check_arg0, undef, \&check_arg2)

This comes in handy when contracting an object method where the first passed argument is the object itself and need not being checked:

    # method perimeter on obect MyCircle expects no
    # arguments, but method color expects a color code
    contract('perimeter')->in(undef)->enable;

    contract('color')
        ->in(undef, sub { return defined $_[0] && ref $_[0] eq 'MyCircle::ColorCode'})
        ->enable;

You can also constraint arguments passed in hash-style, and it look like this:

    # function add expects a hash with 2 keys 'a' and 'b'
    # having values that are integers
    contract('add')
        ->in(a => \&is_integer, b => \&is_integer)
        ->enable;

If add was a method on an object, in() would look like:

    contract('add')
        ->in(undef, a => \&is_integer, b => \&is_integer)
        ->enable;

Finally, you can mix list- and hash-style argument passing. Say that add() expects first 2 arguments then a hash of 2 keys with 2 values, and all must be integers:

    contract('add')
        ->in(\&is_integer,
             \&is_integer,
             a => \&is_integer,
             b => \&is_integer)
        ->enable;

Most of the constraints on arguments will in fact act like type constraints and be the same all across your contracts. Instead of declaring again and again the same anonymous sub in every contract, create a function that tests this specific type, such as is_integer. Give those functions names that show which types they test, such as is_integer, is_string, is_date, is_arrayref and so on. It is also a good idea to gather all those functions in one specific module to import together with Sub::Contract.

If you don't want to check whether the argument is defined or not in every constraint, you may want to use defined_and and undef_or (see further down).

$contract->out(@checks)

Same as in but for validating return arguments one by one.

out() validates return values in a context sensitive way. See 'Contract and context' under 'Discussion' for details.

The syntax of @checks is the same as for in().

$contract->memoize

Enable memoization of the contractor's results.

TODO: detail arguments

$contract->flush_cache

Empty the contractor's cache of memoized results.

$contract->enable

Compile and enable a contract. If the contract is already enabled, it is first disabled, then re-compiled and enabled.

Enabling the contract consists in dynamically generating some code that validates the contract before and after calls to the contractor and wrapping this code around the contractor.

$contract->disable

Disable the contract: remove the wrapper code generated and added by enable from around the contractor.

$contract->is_enabled

Return true if this contract is currently enabled.

$contract->contractor

Return the fully qualified name name of the subroutine affected by this contract.

$contract->contractor_cref

Return a code reference to the contracted subroutine.

$contract->reset

Remove all previously defined constraints from this contract and disable memoization. reset has no effect on the contract validation code as long as you don't call enable after reset. reset is usefull if you want to redefine a contract from scratch during runtime.

Class API

contract($qualified_name)

Same as new Sub::Contract($qualified_name). Must be explicitly imported:

    use Sub::Contract qw(contract);

    contract('add_integers')
        ->in(\&is_integer, \&is_integer)
        ->enable;

    sub add_integers {...}
undef_or($coderef)

Syntax sugar to allow you to specify a constraint on an argument saying 'this argument must be undefined or validate this test'.

Assuming you have a test function is_integer that passes if its argument is an integer and croaks otherwise, you could write:

    use Sub::Contract qw(contract undef_or);

    # set_value takes only 1 argument that must be either
    # undefined or be validated by is_integer()
    contract('set_value')
        ->in(undef_or(\&is_integer))
        ->enable;

    sub set_value {...}
defined_and($coderef)

Syntax sugar to allow you to specify a constraint on an argument saying 'this argument must be defined and validate this test'.

Example:

    use Sub::Contract qw(contract defined_and undef_or);

    # set_name takes a hash that must contain a key 'name'
    # that must be defined and validate is_word(), and may
    # contain a key 'nickname' that can be either undefine
    # or must validate is_word().
    contract('set_name')
        ->in( name => defined_and(\&is_word),
              nickname => undef_or(\&is_word) )
        ->enable;

   sub set_name {...}
is_a($pkg)

Returns a subroutine that takes 1 argument and returns true if this argument is an instance of $pkg and false if not.

Example:

    # argument 'name' must be an instance of String::Name
    contract('set_name')
        ->in( name => is_a("String::Name") )
        ->enable;

   sub set_name {...}

Class variables

The value of the following variables is set by Sub::Contract before executing any contract validation code. They are designed to be used inside the contract validation code and nowhere else!

$Sub::Contract::wantarray

1 if the contractor is called in array context, 0 if it is called in scalar context, and undef if called in no context. This affects the value of Sub::Contract::results.

@Sub::Contract::args

The input arguments that the contractor is being called with.

@Sub::Contract::results

The result(s) returned by the contractor, as seen by its caller. Can also be accessed with the exported function 'returns'.

The following example code uses those variables to validate that a function foo returns 'array' in array context and 'scalar' in scalar context:

    use Sub::Contract qw(contract results);

    contract('foo')
        ->post(
            sub {
                 my @results = returns;

                 if ($Sub::Contract::wantarray == 1) {
                     return defined $results[0] && $results[0] eq "array";
                 } elsif ($Sub::Contract::wantarray == 0) {
                     return defined $results[0] && $results[0] eq "scalar";
                 } else {
                    return 1;
                 }
             }
        )->enable;

SEE ALSO

See Carp::Datum, Class::Agreement, Class::Contract.

BUGS

See 'Issues with contract programming' under 'Discussion'.

VERSION

$Id: Contract.pm,v 1.21 2008/05/24 20:40:34 erwan_lemonnier Exp $

AUTHORS

Erwan Lemonnier <erwan@cpan.org>, as part of the Pluto developer group at the Swedish Premium Pension Authority.

LICENSE AND DISCLAIMER

This code was developed at the Swedish Premium Pension Authority as part of the Authority's software development activities. This code is distributed under the same terms as Perl itself. We encourage you to help us improving this code by sending feedback and bug reports to the author(s).

This code comes with no warranty. The Swedish Premium Pension Authority and the author(s) decline any responsibility regarding the possible use of this code or any consequence of its use.