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

I started learning this new cool feature, but after few hours of reading and trying I don't know how to program more complicated check functions. Is it possible to use one check function as part of another function? Let's assume I have function, which expects two references as parameters: ref to simple array of scalars and ref to AoA of scalars. My validation module looks like this:
package V; use Carp; sub is_array_of_scalar { my $arg = shift; if (!defined $arg) { warn "Undefined value! Expected reference to an array of scala +rs!"; return 0 } if(ref $arg ne "ARRAY") { warn "Expected ARRAY reference, found " . ref($arg) . "."; return 0; } for my $index(0..$#$arg) { if(!defined($arg[$index])) { warn "Undefined value found on position $index."; return 0 } if(ref($arg[$index]) ne "") { warn "Scalar expected, reference found on position $index. +"; # (1) return 0 } } return 1; } sub is_array_of_array_of_scalar { my $arg = shift; if (!defined $arg) { warn "Undefined value! Expected reference to an array of scala +rs!"; return 0 } if(ref $arg ne "ARRAY") { warn "Expected ARRAY reference, found " . ref($arg) . "."; return 0; } for my $index(0..$#$arg) { if(!is_array_of_scalar($arg[$index])) { warn "Wrong value(s) in row $index."; return 0 } } return 1; } "true value at the very end";

And here is the problem: Is it possible to say precisely, i.e. that some wrong value is in row 5, column 3? Above code won't do it. Function is_array_of_scalar doesn't know, which column it is checking. Is the Sub::Contract module proper to perform such checks or rather to do simpler (and not nested) ones only?

Update: $_ corrected in loop, thanks JadeNB

Replies are listed 'Best First'.
Re: proper Sub::Contract use
by JadeNB (Chaplain) on Sep 01, 2008 at 17:27 UTC
    A few comments on your code:
    • The line
      warn "Expected ARRAY reference, found " . ref($arg) . ".";
      will print, for example, 'Expected ARRAY reference, found .' if its argument is not a reference (because then ref $arg is the empty string). You may find it better to do a further check about whether $arg is a reference at all, and tailor the error message accordingly.
    • In the body of the for loop that starts
      for my $index(0..$#$arg)
      you work with the variable $_. By default, for ( LIST ) does alias $_ to the elements of LIST in turn; but, if you pass in an explicit variable name, then that'll be the name that gets aliased. Also, since you work directly with $_, rather than using it as a subscript to @$arg, it probably makes more sense to start your loop as for my $entry ( @$arg ). (Because of Perl's internal storage, it's faster to loop directly over an array than to loop over indices and perform lots of subscripting operations.)
    • Instead of if ( ref($_) ne "" ), you could just test if ( ref($_) ). The empty string is false, and (I think) no other return value of ref is.
    • Why do you have (1) at the end of the line
      warn "Scalar expected, reference found on position $index."; (1)
      ?

    As to your bigger question, if you want to identify the 'row' as well as the 'column' where the wrong value occurs, you could let information flow either way: You could pass &array_of_scalars the index of the row it's checking, so that it can incorporate that in the error message; or you could provide a way (by exception-throwing, probably) for &array_of_scalars to indicate to its caller the objectionable column, and adjust &array_of_array_of_scalar's error message accordingly. Also, yes, it looks (at a cursory scan) as if Sub::Contract can do what you want; but, if you are just concerned with checking for AoAs, then a simple grep is probably closer to what you want.

    UPDATE: On further reading, Sub::Contract still requires you to write your own tests; and, since it says of error messages:

    When a call to a contractor breaks the contract, the constraint code will return false or croak. If it returns false, Sub::Contract will emit an error looking as if the contractor croaked.
    it sounds as if it won't give (on its own) the sort of specific error messages you want. Thus, you'll still need to write the tests (which you've mostly already done). If you want (as you seem to do, and should!) to use a module rather than rolling your own, Data::Validate::Struct might help (although, again, it seems to be going a bit overboard in this case).

      Thanks for the comments. $_ is corrected (my mistake) and (1) was there because I wanted to write about this line, as it should print "Scalar expected, reference found on position $index." when called normally, and "Scalar expected, reference found on position $index in row $row." when called from &is_array_of_array_of_scalar(). Just problem of finding context.

      Structures will grow and AoA will be part of another bigger one, so simple grep won't be enough.

      I tried exceptions mechanism, but I think It has no use here - there is no way to handle exceptions outside validation functions. I decided just to use warn "in cascades", so I get info message like this:

      V::is_array_of_scalar: Scalar expected, HASH reference found on positi +on 2. Input: $VAR1 = [ 'abc', 'def', { 'some' => 'any' }, 'x', 'something' ]; V::is_array_of_array_of_scalar: Wrong value(s) in row 1. at scripts/li +b/V.pm line 102. input argument of [FDFr::compareArr] for key '-elem2' fails its contra +ct constraint at (eval 12) line 31

      but it will be pretty unreadable as V module will grow. So finally your hint about passing additional argument seems to be the best solution. If the function is called as top-level validator (no possibility to pass additional arguments), it will just print its error message, but if is called by other validation function, passing arguments is possible to pass any arguments and change messages. Thanks for ideas, I'm going back to work :)

        I tried exceptions mechanism, but I think It has no use here - there is no way to handle exceptions outside validation functions. I decided just to use warn "in cascades", so I get info message like this:
        I think that your idea of warning in cascades is a good solution to the problem, so I don't want to discourage it, but I am confused by your qualification. What do you mean when you say that there is no way to handle exceptions outside validation functions? Unless I misunderstand what you're doing, all you have to do is wrap every validation in an eval {}, and check afterwards whether you caught an exception—which is not much more complicated than checking for truth or falsity anyway.