premchai21 has asked for the wisdom of the Perl Monks concerning the following question:

Before anyone admonishes me for reinventing the wheel, let me say that I searched CPAN and found nothing that did what I need this module to do.

What I need is a way to, possibly recursively, check structures for type -- for instance, one should be able to check if it matches certain regexen, for strings, or isa a certain class, or isa a certain type, or contains only elements or keys of certain types, or returns true when given as the argument to a supplied coderef, etc. So I planned it out and coded it. Originally I was going to use Quantum::Superpositions rather than grep here, but it produced copious amounts of warnings with -w, so I used grep instead, which didn't.

The problem I'm having is that when given certain pieces of data that should not match the template given, it returns true anyway.

The relevant section of code:

sub typeck { my ($what, $type) = @_; if (!UNIVERSAL::isa($type, 'ARRAY')) { croak 'Type not an array re +ference' } not grep { !$_ } ( map { grep { $_ } ( (index($_, '/') == 1 and rindex($_, '/') == (length($_) - 1 +) and do { my $rex = substr($_, 1, length($_) - 1); (ref($what) eq '') ? ($what =~ $rex) : ((Denter $what) =~ $ +rex); }), (UNIVERSAL::isa($_, 'ARRAY') && do { my $type = $_; not grep {!$_} (map { typeck ($_, $type) } ((UNIVERSAL::isa($what, 'ARRAY' +)) ? @$what : values(%$what))); }), (UNIVERSAL::isa($_, 'HASH') && do { my $type = $_; not grep {!$_} (map { exists ($type->{$_}) } (keys %$what)); }), (UNIVERSAL::isa($_, 'CODE') && $_->($what)), (UNIVERSAL::isa($what, $_))) } (@$type)); }
The data and type template, Data::Dentered:
# -- data --
@
    10
    345
    badvalue
    49120
# -- type --
@
    ARRAY
    @
        /^0-9+$/
When run, &typeck seems to be skipping entirely over the template, returning 1 always. I looked through the code and found no errors, and the Perl debugger was of no help, so I turn to the monks for help.

Can anyone figure out what is going on here?

Replies are listed 'Best First'.
Re: Type checker not checking out
by clemburg (Curate) on Jul 31, 2001 at 18:22 UTC

    I would attack this problem differently, by use of higher-level functions (predicates).

    Like this:

    #!/usr/bin/perl -w use strict; sub map_recursive { my ($structure, $predicate) = @_; my @result; if (UNIVERSAL::isa($structure, 'ARRAY')) { for (@{$structure}) { push @result, map_recursive($_, $predicate); } } elsif (UNIVERSAL::isa($structure, 'HASH')) { for (keys %{$structure}) { push @result, map_recursive($structure->{$_}, $predicate); } } else { push @result, $predicate->($structure); } return @result; } my $scalar = 2; my @list = (1, 3, 8, 10); my %hash = (a => 1, b => 2, c => 3); my $oddp = sub { (((shift) % 2) == 1) ? 1 : 0 }; print "#1: ", join(",", map_recursive($scalar, $oddp)), "\n"; print "#1: ", join(",", map_recursive(\@list, $oddp)), "\n"; print "#1: ", join(",", map_recursive(\%hash, $oddp)), "\n";

    Then you can hide the complexity of actually checking the type in the predicate.

    Christian Lemburg
    Brainbench MVP for Perl
    http://www.brainbench.com

      Hmm... but what if I need to check for, say, an array of objects of a certain type? Suppose I do this:
      package Foo; sub new { bless {}, shift } sub get { $_[0]->{$_[1]} } sub set { $_[0]->{$_[1]} = $_[2] } package main; my @list = (Foo->new, Foo->new, Foo->new); $list[0]->set('a', 'b'); $list[1]->set('b', 'a'); $list[2]->set('x', 'y'); my $isa_foo = sub { UNIVERSAL::isa(shift, 'Foo') }; print join ',', map_recursive(\@list, $isa_foo);
      This doesn't work, because map_recursive will descend an extra level into the Foo structure itself, and give me intermingled 1s and 0s which have no meaning to me or my program whatsoever.

      It descends into the Foo, finding it to be a hash, and declares 0 on 'a', 'b', and 'y', since they aren't Foos. Or:

      my @list2 = ( {a => 'm'}, { 1 => 55}, { Foo => 'Bar' }); my $h = sub { UNIVERSAL::isa(shift, 'HASH') }; print join ',', map_recursive (\@list2, $h);
      And here I'm looking to check for an array of hashes. But oh my, 55 isn't a hash, so this gets disqualified. You see now why map_recursive won't do; the format I'm using allows one to specify the whole structure.

      However, it doesn't quite work yet...

      Update: clemburg's solution below works better than the one above. I think I'll probably use that. Thanks you, clemburg.

        Easy. Change map_recursive() to not descend into recognized structures. That is probably the more sensible default behavior, agreed.

        #!/usr/bin/perl -w use strict; sub map_recursive { my ($structure, $predicate) = @_; # don't descend into recognized structures if ($predicate->($structure)) { return $predicate->($structure); } # not recognized - pick apart my @result; if (UNIVERSAL::isa($structure, 'ARRAY')) { for (@{$structure}) { push @result, map_recursive($_, $predicate); } } elsif (UNIVERSAL::isa($structure, 'HASH')) { for (keys %{$structure}) { push @result, map_recursive($structure->{$_}, $predicate); } } else { push @result, $predicate->($structure); } return @result; } package Foo; sub new { bless {}, shift } sub get { $_[0]->{$_[1]} } sub set { $_[0]->{$_[1]} = $_[2] } package main; my @list = (Foo->new, Foo->new, Foo->new); $list[0]->set('a', 'b'); $list[1]->set('b', 'a'); $list[2]->set('x', 'y'); my $isa_foo = sub { UNIVERSAL::isa(shift, 'Foo') }; print "#1: ", join(',', map_recursive(\@list, $isa_foo)), "\n"; my @list2 = ( {a => 'm'}, { 1 => 55}, { Foo => 'Bar' }); my $h = sub { UNIVERSAL::isa(shift, 'HASH') }; print "#2: ", join(',', map_recursive (\@list2, $h)), "\n";

        Christian Lemburg
        Brainbench MVP for Perl
        http://www.brainbench.com