+ my $atom = $a->qualified_name;
+
+ my $version = $a->version;
+ $atom = $a->range . $atom . '-' . $version if defined $version;
+
+ return $atom;
+}
+
+my %order = (
+ '<' => -2,
+ '<=' => -1,
+ '=' => 0,
+ '>=' => 1,
+ '>' => 2,
+);
+
+=head2 C<and @atoms>
+
+Compute the ranged atom representing the logical AND between C<@atoms> with the same category and name.
+
+=cut
+
+sub and {
+ shift unless length ref $_[0];
+
+ my $a1 = shift;
+ return $a1 unless @_;
+
+ my $a2 = shift;
+ $a2 = $a2->and(@_) if @_;
+
+ my $p1 = $a1->qualified_name;
+ my $p2 = $a2->qualified_name;
+ Carp::confess("Atoms for different packages $p1 and $p2") unless $p1 eq $p2;
+
+ my $v1 = $a1->version;
+ return $a2 unless defined $v1;
+ my $r1 = $a1->range; # Defined if $v1 is defined
+
+ my $v2 = $a2->version;
+ return $a1 unless defined $v2;
+ my $r2 = $a2->range; # defined if $v2 is defined
+
+ my $o1 = $order{$r1};
+ my $o2 = $order{$r2};
+
+ Carp::confess("Incompatible ranges $r1$p1 and $r2$p2") if $o1 * $o2 < 0;
+
+ if ($r2 eq '=') {
+ ($a1, $a2) = ($a2, $a1);
+ ($v1, $v2) = ($v2, $v1);
+ ($r1, $r2) = ($r2, $r1);
+ ($o1, $o2) = ($o2, $o1);
+ }
+
+ if ($r1 eq '=') {
+ my $r = $r2 eq '=' ? '==' : $r2;
+ Carp::confess("Version mismatch $v1 $r $v2") unless eval "\$a1 $r \$a2";
+ return $a1;
+ } elsif ($o1 > 0) {
+ return $a1 < $a2 ? $a2 : $a1;
+ } else {
+ return $a1 < $a2 ? $a1 : $a2;
+ }
+}
+
+=head2 C<fold @atoms>
+
+Returns a list built from C<@atoms> but where there's only one atom for a given category and name.
+
+=cut
+
+sub fold {
+ shift unless length ref $_[0];
+
+ my %seen;
+ for my $atom (@_) {
+ my $key = $atom->qualified_name;
+
+ my $cur = $seen{$key};
+ $seen{$key} = defined $cur ? $cur->and($atom) : $atom;
+ }
+
+ return map $seen{$_}, sort keys %seen;