File Coverage

lib/Sub/IsEqual.pm
Criterion Covered Total %
statement 37 38 97.3
branch 19 20 95.0
condition 10 11 90.9
subroutine 9 9 100.0
pod 1 1 100.0
total 76 79 96.2


line stmt bran cond sub pod time code
1             package Sub::IsEqual;
2              
3             =head1 NAME
4              
5             Sub::IsEqual - determine if two arguments are equal
6              
7             =cut
8              
9 1     1   662 use strict;
  1         1  
  1         46  
10 1     1   7 use warnings;
  1         2  
  1         48  
11              
12 1     1   17 use Exporter qw{import};
  1         2  
  1         38  
13 1     1   6 use List::Util qw{first};
  1         2  
  1         118  
14 1     1   6 use Scalar::Util qw{refaddr};
  1         2  
  1         69  
15 1     1   664 use Set::Functional qw{symmetric_difference};
  1         2191  
  1         357  
16              
17             =head1 VERSION
18              
19             Version 0.03
20              
21             =cut
22              
23             our $VERSION = '0.03';
24              
25             =head1 SYNOPSIS
26              
27             This module provides a function called is_equal to determine if any two
28             arbitrary arguments are the same. Equality is determined by definedness,
29             structure, and string equality, so 1 and 1.0 will be considered inequal.
30             For data structures, circular references will be detected.
31              
32             =cut
33              
34             =head1 METHODS
35              
36             =cut
37              
38             our @EXPORT_OK = qw{is_equal};
39              
40             =head2 is_equal
41              
42             Given 2 arguments, determine if they are equivalent using string equality
43             and deep comparison. For large data structures, is_equal will attempt to
44             walk the structure, comparing all key-value paris for hashes, checking the
45             order in arrays, and following all references while checking for loops.
46             Blessed objects must be the same value in memory, by default, but may define
47             their own equivalence by overloading the eq operator. The only exception
48             to all of this is undef, which is only equivalent to itself.
49              
50             Examples:
51              
52             is_equal(undef, undef); # => true
53             is_equal(undef, ''); # => false
54             is_equal(1, 1.0); # => false
55             is_equal("mom", "mom"); # => true
56             is_equal([qw{hello world}], [qw{hello world}]); # => true
57             is_equal({hello => 1}, {hello => 1}); # => true
58              
59             =cut
60              
61             sub is_equal {
62 74     74 1 867 my ($left, $right, $recursion_check) = @_;
63              
64             #Check that both values are in the same state of definedness
65 74 100       145 return 0 if defined($left) ^ defined($right);
66             #Check that both values are defined
67 72 100       112 return 1 if ! defined($left);
68             #Check that both values are string equivalent
69 71 100       235 return 1 if $left eq $right;
70              
71 33         55 my ($left_ref, $right_ref) = (ref($left), ref($right));
72              
73             #Check that both values refer to the same type of thing
74 33 100       67 return 0 if $left_ref ne $right_ref;
75             #Check that both values are references
76 30 100       76 return 0 if $left_ref eq '';
77              
78 25   100     82 $recursion_check ||= {};
79 25         69 my ($left_refaddr, $right_refaddr) = (refaddr($left), refaddr($right));
80              
81             #Check that both references are in the same visit state
82 25 100       71 return 0 if exists $recursion_check->{$left_refaddr} ^ exists $recursion_check->{$right_refaddr};
83             #Check that both references have already been visited
84 24 100       50 return 1 if exists $recursion_check->{$left_refaddr};
85              
86 20         46 undef $recursion_check->{$left_refaddr};
87 20         31 undef $recursion_check->{$right_refaddr};
88              
89             #Check that scalar references point to the same values
90 20 100 66     94 if ($left_ref eq 'SCALAR' || $left_ref eq 'REF') {
    100          
    50          
91 2         8 return is_equal($$left, $$right, $recursion_check);
92              
93             #Check that arrays have the same values in the same order
94             } elsif ($left_ref eq 'ARRAY') {
95             return
96             @$left == @$right
97 13   100 39   115 && ! defined(first { ! is_equal($left->[$_], $right->[$_], $recursion_check) } (0 .. $#$left));
  39         76  
98              
99             #Check that hashes contain the same keys pointing to the same values
100             } elsif ($left_ref eq 'HASH') {
101             return
102             ! symmetric_difference([keys %$left], [keys %$right])
103 5   100 7   26 && ! defined(first { ! is_equal($left->{$_}, $right->{$_}, $recursion_check) } keys %$left);
  7         94  
104              
105             #Give up
106             } else {
107 0         0 die "Must define string equality for type [$left_ref]";
108             }
109             }
110              
111             =head1 AUTHOR
112              
113             Aaron Cohen, C<< >>
114              
115             =head1 ACKNOWLEDGEMENTS
116              
117             This module was made possible by L
118             (L<@ShutterTech|https://twitter.com/ShutterTech>). Additional open source
119             projects from Shutterstock can be found at
120             L.
121              
122             =head1 BUGS
123              
124             Please report any bugs or feature requests to C, or through
125             the web interface at L. I will
126             be notified, and then you'll automatically be notified of progress on your bug as I make changes.
127              
128             =head1 SUPPORT
129              
130             You can find documentation for this module with the perldoc command.
131              
132             perldoc Sub::IsEqual
133              
134             You can also look for information at:
135              
136             =over 4
137              
138             =item * Official GitHub Repo
139              
140             L
141              
142             =item * GitHub's Issue Tracker (report bugs here)
143              
144             L
145              
146             =item * CPAN Ratings
147              
148             L
149              
150             =item * Official CPAN Page
151              
152             L
153              
154             =back
155              
156             =head1 LICENSE AND COPYRIGHT
157              
158             Copyright 2013 Aaron Cohen.
159              
160             This program is free software; you can redistribute it and/or modify it
161             under the terms of either: the GNU General Public License as published
162             by the Free Software Foundation; or the Artistic License.
163              
164             See http://dev.perl.org/licenses/ for more information.
165              
166             =cut
167              
168              
169             1;