NAME

FP::Equal - generic equality comparison

SYNOPSIS

    use FP::Equal;
    use FP::List;
    use FP::Div qw(inc);

    ok equal [1, list(2, 3)], [1, list(1, 2)->map(*inc)];
    is equal( [1, list(2, 3)], [1, list(1, 2)] ),
       ''; # false but defined since same type
    is equal( [1, list(2, 3)], [1, list([], 3)] ),
       undef; # to say it's not the same type

    # equal forces any promises that it encounters:
    use FP::Lazy; use FP::List 'pair';
    ok equal lazy{pair 3, lazy{2 + 1}},
             pair(3, 2 + 1);

    use FP::Stream 'string_to_stream';
    is equal( string_to_stream("Hello"),
              string_to_stream("Hello1") ),
       undef; # not the same type at the end, null vs. pair;
              # although this may be subject to change

    # n-ary variant of equal (not done in equal itself for performance
    # reasons and to allow for prototype declaration):
    use FP::Equal qw(equaln);
    is equaln("a", "a", "a"), 1;
    is equaln("a", "a", "b"), '';

    # For writing tests with Test::More--the same as `is` but uses
    # `equal` for comparisons, and shows values in failures via
    # `show`:
    use FP::Equal qw(is_equal);
    is_equal list(1+1), list(2);

    # `equal` is giving an exception if 2 objects of the (~)same class
    # are given, but the class doesn't implement the protocol:
    is_equal [ scalar eval {
                  equal bless([1+1], 'FP::Equal::EXAMPLE'),
                        bless([2], 'FP::Equal::EXAMPLE')
               },
               length("$@")>5 ],
             [undef, 1];

    # `relaxedequal` does not throw exceptions, it falls back to
    # FP::DumperEqual if a class doesn't implement the protocol:
    use FP::Equal qw(relaxedequal);
    is_equal [ scalar eval {
                  relaxedequal
                        bless([1+1], 'FP::Equal::EXAMPLE'),
                        bless([2], 'FP::Equal::EXAMPLE')
               },
               length("$@") ],
             [1, 0];

DESCRIPTION

Deep, generic (class controlled) structure equality comparison.

Non-objects are hard coded in this module. Objects are expected to have an `FP_Equal_equal` method that is able to take an argument of the same class as the object to compare. If it doesn't have such an object, it simply can't be compared using the equal* functions, they will give "no such method" exceptions; relaxedequal falls back to using FP::DumperEqual (which uses Data::Dumper) in such cases.

This does *name based* type comparison: structurally equivalent objects do not count as equal if they do not have the same class (or more general, reference name); the `FP_Equal_equal` method is not even called in this case, and the functions return undef. This might be subject to change: certain pairs of types will be fine to compare; let the classes provide a method that checks whether a type is ok to compare?

TODO

- cycle detection

- immutable version -> equals_now equals_forever

- do we need the possibility for "context" dependent (and not by way of subclassing and overriding equals_*) equality comparisons?

SEE ALSO

FP::Abstract::Equal for the protocol definition

FP::DumperEqual used as fallback in relaxedequal.

FP::Show

NOTE

This is alpha software! Read the status section in the package README or on the website.