| Match-Smart documentation | Contained in the Match-Smart distribution. |
Match::Smart - best-guess matching of two scalars
use Match::Smart qw/ smart_match /;
if (smart_match( $user, qr/^dan/ )) {
print "You might be a dan\n";
}
if (smart_match( $amount, sub { $_[0] < 10} )) {
print "$amount is less than 10\n";
}
Match::Smart provides a easy means of testing whether two scalars match. A
best guess on how they should be compared is made, based on the types of the
two scalars.
The means of matching is based heavily on the Apocalypse 4, Perl 6 document by Larry Wall.
Nothing is exported by default. The smart_match() subroutine can be exported
if desired.
Attempts to do the right thing in matching the two given values. Returns true if the two values match, false otherwise.
If both values are references, referring to the same thing, they are assumed to match.
The table below describes how the comparisons are performed. Those described
using grep only do so for conciseness. The implementation uses for loops
to allow short-circuiting as soon as a match is determined.
The order in which the values are passed to smart_match() only affects the
return value when comparing two CODE references. Therefore, the table below
only shows half of the necessary comparisons (those missing can be inferred).
Those noted as '-' are not currently defined and will result in fatal run-time exceptions.
$val1 $val2 true if ...
===== ===== ===========
ARRAY ARRAY any in @{$val1} smart_match any in @{$val2}
ARRAY CODE $val2->(@{$val1})
ARRAY HASH grep { $val2->{$_} } @{$val1}
ARRAY NUMBER $val1->[$val2]
ARRAY OBJECT grep { smart_match($_, $val2) } @{$val1}
ARRAY REF grep { smart_match($_, ${$val2}) } @{$val1}
ARRAY REGEXP grep { smart_match($_, $val2) } @{$val1}
ARRAY SCALAR grep { smart_match($_, ${$val2}) } @{$val1}
ARRAY STRING grep { smart_match($_, $val2) } @{$val1}
ARRAY UNDEF grep { smart_match($_, $val2) } @{$val1}
CODE CODE $val1->(&{$val2})
CODE HASH $val1->(%{$val2})
CODE NUMBER $val1->($val2)
CODE OBJECT $val1->($val2)
CODE REF smart_match($val1, ${$val2})
CODE REGEXP $val1->($val2)
CODE SCALAR smart_match($val1, ${$val2})
CODE STRING $val1->($val2)
CODE UNDEF $val1->($val2)
HASH HASH grep { exists $val1->{$_} } keys %{$val2}
HASH NUMBER $val1->{$val2}
HASH OBJECT -
HASH REF smart_match($val1, ${$val2})
HASH REGEXP grep { $_ =~ $val2 } keys %{$val1}
HASH SCALAR smart_match($val1, ${$val2})
HASH STRING $val1->{$val2}
HASH UNDEF -
NUMBER NUMBER $val1 == $val2
NUMBER OBJECT -
NUMBER REF smart_match($val1, ${$val2})
NUMBER REGEXP $val1 =~ $val2
NUMBER SCALAR smart_match($val1, ${$val2})
NUMBER STRING $val1 == $val2
NUMBER UNDEF ** ALWAYS FALSE **
OBJECT OBJECT $val1 == $val2
OBJECT REF smart_match($val1, ${$val2})
OBJECT REGEXP -
OBJECT SCALAR smart_match($val1, ${$val2})
OBJECT STRING $val1->can($val2) && $val1->$val2
OBJECT UNDEF ** ALWAYS FALSE **
REF REF smart_match(${$val1}, ${$val2})
REF REGEXP smart_match(${$val1}, $val2)
REF SCALAR smart_match(${$val1}, ${$val2})
REF STRING smart_match(${$val1}, $val2)
REF UNDEF smart_match(${$val1}, $val2)
REGEXP REGEXP $val1 eq $val2
REGEXP SCALAR smart_match($val1, ${$val2})
REGEXP STRING $val2 =~ $val1
REGEXP UNDEF ** ALWAYS FALSE **
SCALAR SCALAR smart_match(${$val1}, ${$val2})
SCALAR STRING smart_match(${$val1}, $val2)
SCALAR UNDEF smart_match(${$val1}, $val2)
STRING STRING $val1 eq $val2
STRING UNDEF ** ALWAYS FALSE **
UNDEF UNDEF ** ALWAYS TRUE **
Class->smart_match() method to get class to fill in requirements)Apocalypse 4: http://www.perl.com/pub/a/2002/01/15/apo4.html?page=2
Daniel B. Boorstein, <danboo@cpan.org>
Copyright 2004 by Daniel B. Boorstein
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
| Match-Smart documentation | Contained in the Match-Smart distribution. |
package Match::Smart; use 5.006; use strict; use warnings; use Carp; use Scalar::Util qw/ looks_like_number refaddr /; require Exporter; our @ISA = qw(Exporter); our %EXPORT_TAGS = ( 'all' => [ qw( smart_match) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our $VERSION = '0.02'; ## initialize match look-up constants my $i; use constant ARRAY => ++$i; use constant CODE => ++$i; use constant HASH => ++$i; use constant NUMBER => ++$i; use constant OBJECT => ++$i; use constant REF => ++$i; use constant REGEXP => ++$i; use constant SCALAR => ++$i; use constant STRING => ++$i; use constant UNDEF => ++$i; ## type constant to name mappings my @type_names; $type_names[ARRAY] = 'ARRAY'; $type_names[CODE] = 'CODE'; $type_names[HASH] = 'HASH'; $type_names[NUMBER] = 'NUMBER'; $type_names[OBJECT] = 'OBJECT'; $type_names[REF] = 'REF'; $type_names[REGEXP] = 'REGEXP'; $type_names[SCALAR] = 'SCALAR'; $type_names[STRING] = 'STRING'; $type_names[UNDEF] = 'UNDEF'; ## assemble look-up table for matching subs my @match_subs; $match_subs[ARRAY ][ARRAY ] = sub { for my $v (@{$_[0]}) { for my $t (@{$_[1]}) { return 1 if _smart_match($v, $t, $_[2]); } } return; }; $match_subs[ARRAY ][CODE ] = sub { $_[1]->(@{$_[0]}) }; $match_subs[ARRAY ][HASH ] = sub { for my $v (@{$_[0]}) { return 1 if $_[1]->{$v} } return; }; $match_subs[ARRAY ][NUMBER] = sub { $_[0]->[$_[1]] }; $match_subs[ARRAY ][OBJECT] = sub { for my $v (@{$_[0]}) { return 1 if _smart_match($v, $_[1], $_[2]); } return; }; $match_subs[ARRAY ][REF ] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[ARRAY ][REGEXP] = sub { for my $v (@{$_[0]}) { return 1 if _smart_match($v, $_[1], $_[2]); } return; }; $match_subs[ARRAY ][SCALAR] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[ARRAY ][STRING] = sub { for my $v (@{$_[0]}) { return 1 if _smart_match($v, $_[1], $_[2]); } return; }; $match_subs[ARRAY ][UNDEF ] = sub { for my $v (@{$_[0]}) { return 1 if _smart_match($v, $_[1], $_[2]); } return; }; $match_subs[CODE ][CODE ] = sub { $_[0]->(&{$_[1]}) }; $match_subs[CODE ][HASH ] = sub { $_[0]->(%{$_[1]}) }; $match_subs[CODE ][NUMBER] = sub { $_[0]->($_[1]) }; $match_subs[CODE ][OBJECT] = sub { $_[0]->($_[1]) }; $match_subs[CODE ][REF ] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[CODE ][REGEXP] = sub { $_[0]->($_[1]) }; $match_subs[CODE ][SCALAR] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[CODE ][STRING] = sub { $_[0]->($_[1]) }; $match_subs[CODE ][UNDEF ] = sub { $_[0]->($_[1]) }; $match_subs[HASH ][HASH ] = sub { for my $v (keys %{$_[1]}) { return 1 if exists $_[0]->{$v}; } return; }; $match_subs[HASH ][NUMBER] = sub { $_[0]->{$_[1]} }; $match_subs[HASH ][OBJECT] = undef; $match_subs[HASH ][REF ] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[HASH ][REGEXP] = sub { for my $v (keys %{$_[0]}) { return 1 if $v =~ $_[1]; } return; }; $match_subs[HASH ][SCALAR] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[HASH ][STRING] = sub { $_[0]->{$_[1]} }; $match_subs[HASH ][UNDEF ] = undef; $match_subs[NUMBER][NUMBER] = sub { $_[0] == $_[1] }; $match_subs[NUMBER][OBJECT] = undef; $match_subs[NUMBER][REF ] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[NUMBER][REGEXP] = sub { $_[0] =~ $_[1] }; $match_subs[NUMBER][SCALAR] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[NUMBER][STRING] = sub { no warnings 'numeric'; $_[0] == $_[1] }; $match_subs[NUMBER][UNDEF ] = sub { return }; $match_subs[OBJECT][OBJECT] = sub { $_[0] == $_[1] }; $match_subs[OBJECT][REF ] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[OBJECT][REGEXP] = undef; $match_subs[OBJECT][SCALAR] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[OBJECT][STRING] = sub { my $m = $_[1]; $_[0]->can($m) && $_[0]->$m }; $match_subs[OBJECT][UNDEF ] = sub { return }; $match_subs[REF ][REF ] = sub { _smart_match(${$_[0]}, ${$_[1]}, $_[2]) }; $match_subs[REF ][REGEXP] = sub { _smart_match(${$_[0]}, $_[1], $_[2]) }; $match_subs[REF ][SCALAR] = sub { _smart_match(${$_[0]}, $_[1], $_[2]) }; $match_subs[REF ][STRING] = sub { _smart_match(${$_[0]}, $_[1], $_[2]) }; $match_subs[REF ][UNDEF ] = sub { _smart_match(${$_[0]}, $_[1], $_[2]) }; $match_subs[REGEXP][REGEXP] = sub { $_[0] eq $_[1] }; $match_subs[REGEXP][SCALAR] = sub { _smart_match($_[0], ${$_[1]}, $_[2]) }; $match_subs[REGEXP][STRING] = sub { $_[1] =~ $_[0] }; $match_subs[REGEXP][UNDEF ] = sub { return }; $match_subs[SCALAR][SCALAR] = sub { _smart_match(${$_[0]}, ${$_[1]}, $_[2]) }; $match_subs[SCALAR][STRING] = sub { _smart_match(${$_[0]}, $_[1], $_[2]) }; $match_subs[SCALAR][UNDEF ] = sub { _smart_match(${$_[0]}, $_[1], $_[2]) }; $match_subs[STRING][STRING] = sub { $_[0] eq $_[1] }; $match_subs[STRING][UNDEF ] = sub { return }; $match_subs[UNDEF ][UNDEF ] = sub { return 1 }; ## sole interface subroutine sub smart_match { _smart_match(@_[0,1], {}); } ## private (by convention) internal subroutine sub _smart_match { my ($val1, $val2, $seen) = @_; ## pre-emptive checks and recursion check my $addr1 = refaddr($val1); my $addr2 = refaddr($val2); if (defined $addr1 && defined $addr2) { ## return true if two references are equal return 1 if $addr1 == $addr2; ## return false if we repeat a comparison if ($addr1 > $addr2) { return if $seen->{$addr1}{$addr2}++; } else { return if $seen->{$addr2}{$addr1}++; } } ## determine the input types my $val1_type = _identify_type($val1); my $val2_type = _identify_type($val2); ## ensure that we can identify the input types confess "unrecognized type for val1: $val1" unless defined $val1_type; confess "unrecognized type for val2: $val2" unless defined $val2_type; ## re-order arguments for dynamically assigned match routines if ($val1_type > $val2_type) { ($val1, $val2) = ($val2, $val1); ($val1_type, $val2_type) = ($val2_type, $val1_type); } ## ensure that we have a means of testing them confess "smart_match($type_names[$val1_type], $type_names[$val2_type]): " . "comparison not defined" unless defined $match_subs[$val1_type][$val2_type]; ## test the arguments return $match_subs[$val1_type][$val2_type]->($val1, $val2, $seen); } ## private (by convention) type identification subroutine sub _identify_type { my $ref = ref $_[0]; return ! defined $_[0] ? UNDEF : $ref eq 'ARRAY' ? ARRAY : $ref eq 'CODE' ? CODE : $ref eq 'HASH' ? HASH : $ref eq 'Regexp' ? REGEXP : $ref eq 'SCALAR' ? SCALAR : $ref eq 'REF' ? REF : $ref eq 'GLOB' ? () : $ref eq 'LVALUE' ? () : $ref ? OBJECT : looks_like_number($_[0]) ? NUMBER : STRING ; } 1; __END__