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

use strict;
use warnings;

use Test::More tests => 4;
use Test::Exception;

local $TODO = 'higher-order is unfinished';

#
# Examples from "Contracts for Higher-Order Functions" by Robert Bruce Findler
# and Matthias Felleisen
#
# http://www.ccs.neu.edu/scheme/pubs/icfp2002-ff.pdf 
#

use Class::Agreement;

# helpers for the purposes of example
sub is_greater_than_nine            { $_[0] > 9 }
sub is_between_zero_and_ninety_nine { $_[0] > 0 and $_[0] < 99 }

# g : (integer -> integer) -> integer
# (define/contract g
#   ((greater-than-nine? -> between-zero-and-ninety-nine?)
#     -> #     between-zero-and-ninety-nine?)
#   (lambda (f) (f 0)))

my @lines = map { __LINE__ + $_ } 4, 5, 6, 9;

sub make_contract {
    my ($name) = @_;
    precondition $name => sub {    # $lines[0]
        precondition $_[0] => sub { is_greater_than_nine( $_[0] ) };    # $lines[1]
        postcondition $_[0] =>                                          # $lines[2]
            sub { is_between_zero_and_ninety_nine( $_[0] ) };
    };
    postcondition $name =>
        sub { is_between_zero_and_ninety_nine(result) };                # $lines[3]
}

# "At the point when g invokes f, the is_greater_than_nine portion of g's
# contract fails. According to the even-odd rule, this must be g's fault. In
# fact, g does supply the bad value, so g must be blamed."

make_contract('g');

sub g {
    my ($f) = @_;
    $f->(0);
}

eval {
    g( sub {0} );
};
like $@, qr/function main::g provided invalid input/,
    "g()'s implementation is at fault";
like $@, qr/line $lines[1]/, "contract 1 was broken";

# "Imagine a variation of the above example where g applies f to 10 instead of
# 0. Further, imagine that f returns −10. This is a violation of the result
# portion of g's argument's contract [number 2 above] and, following the
# even-odd rule, the fault lies with g's caller. 

make_contract('g2');

sub g2 {
    my ($f) = @_;
    $f->(10);
}

my $caller_line = __LINE__ + 2;
eval {
    g2( sub { -10 } );
};
like $@, qr/caller of main::g2 provided invalid/, "g()'s caller is at fault";
like $@, qr/line $lines[2]/, "contract 2 was broken";