Carp::Ensure - Ensure a value is of the expected type
use Carp::Ensure( qw( is_a ) );
ensure('string', "Some arbitrary string") if DEBUG; ensure('@integer', 1, 2, 3) if DEBUG; ensure('@\integer', \1, \2, \3) if DEBUG;
my %word2Int = ( one => 1, two => 2, three => 3 ); my @ints = values(%word2Int); my @wordsInts = ( keys(%word2Int), @ints );
ensure('\@integer', \@ints) if DEBUG;
ensure('@word|integer', %word2Int) if DEBUG; ensure('%word=>integer', %word2Int) if DEBUG;
die("Unexpected type") unless is_a('@word|integer', @wordsInts); die("Unexpected type") unless is_a('@\@word|integer', \@wordsInts, [ "four", 4 ]);
# Receives a string, a `Mail::Internet' object, a reference to a hash mapping # strings to integers sub someSub($$%) { ensure([ qw( string Mail::Internet HASH %string=>integer ) ], \@_) if DEBUG; my( $string, $object, %hash ) = @_;
# ... }
Most of the time it's a nice feature, that Perl has no really strict type checking as in C++. However, sometimes you want to ensure, that you subs actually get the type of arguments they expect. Or they return what you expect.
That is where Carp::Ensure may be useful. You can check every value whether it has the type you expect. You may fine tune the type checking from very coarse checking like defined vs. undefined to very detailed checks which check even the keys and values of a hash. In most places you may give alternative types so for instance a parameter can easily be checked to be of a certain type or undefined.
There are checking routines for a few commonly used base types included and you may add your own checking routines so you can check for the types specific to your program.
The types are described by a simple grammar which picks up as much as possible you already know from the Perl type system.
use Carp::Ensure;
ensure("some_type", $value) if DEBUG; ensure("@value_type", @array) if DEBUG; ensure("%key_type=>value_type", %hash) if DEBUG;
ensure([ qw( type1 type2 ... ) ], [ $value1, $value2, ... ]) if DEBUG; ensure([ qw( type1 type2 ... ) ], \@_) if DEBUG;
Checks whether the types described in the first argument are matched by the values given in the following arguments. If the values match the type ensure returns an aribtrary value. If a value doesn't match the specified type, ensure Carp::confesses with an approriate error message and thus stops the program.
If the first argument is a string, it describes the type of the remaining arguments which may be arbitrary many (including none). This is useful for list types (i.e. arrays and hashes) and to check single values.
If the first argument is a reference to an array, the second argument must be a reference to an array, too. In this calling scheme the first array describes the types contained in the second argument. It is particularly useful to check the argument list of a sub.
Care is taken to not change the second argument in any way.
Note, that usually ot only makes sense when the last of the described types checks for a list type. This is because in Perl a list type sucks up all the remaining values.
See TYPE GRAMMAR for how the types are described.
The if DEBUG
concept is taken from the Carp::Assert manpage where it is explained in
detail (particularly in Debugging vs Production in the Carp::Assert manpage. Actually the
DEBUG value is probably shared between the Carp::Assert manpage and this module. So
take care when enabling it in one and disabling it in the other package use
.
In short: If you say use Carp::Ensure
you switch DEBUG on and ensure
works as expected. If you say no Carp::Ensure
then the whole call is
compiled away from the program and has no impact on efficiency.
# Both are possible use Carp::Ensure( qw( :DEBUG is_a ) ); use Carp::Ensure( qw( :NDEBUG is_a ) );
$is_of_type = is_a("some_type", $value); $is_of_type = is_a("@value_type", @array); $is_of_type = is_a("%key_type=>value_type", %hash);
$is_of_type = is_a([ qw( type1 type2 ... ) ], [ $value1, $value2, ... ]); $is_of_type = is_a([ qw( type1 type2 ... ) ], \@_);
This does the same as ensure, however, it only returns true or false instead of Carp::confessing. You can use this to check types of values without immediately stopping the program on failure or to build your own testing subs like this:
sub Carp::Ensure::is_a_word1empty { Carp::Ensure::is_a('word|empty', ${shift()}) }
If a false value is returned $@ is set to an error message. Otherwise $@ is undefined.
You may create rather complex type descriptions from the following grammar.
Since whitespace is not relevant in the grammar, it may occur anywhere outside of identifiers. Actually any whitespace is removed before parsing the type description starts.
hash | array | alternative
'%
' alternative '=
>' alternative
'@
' alternative
simple '|
' alternative | simple
reference | dynamic | special | scalar
'\
' type | class | object | 'HASH
' | 'ARRAY
' | 'CODE
' | 'GLOB
'
Note: Take care with the \
. Even in a string using single quotes a directly
following backslash quotes a backslash! Whitespace between subsequent
backslashes simplifies things greatly.
user
'string
' | 'word
' | 'empty
' | 'integer
' | 'float
' | 'boolean
' | 'regex
'
These common simple types are predefined.
'^
' object
A value matching such a type is a name of a class (i.e. a string) represented by the name matching the regular expression object. This may mean, that the class is a superclass of the class given by the value.
Thus the first parameter of a method which might be used static as well as with an object has a type of
Some::Class|^Some::Class
/^[A-Z]\w*(::\w+)*$/
The value is a object (i.e. a blessed reference) of the class represented by the name matching the regular expression. This may mean, that the class is a superclass of the object's class.
/^[a-z]\w*$/
This might be a string userType matching the regular expression. For this a sub
Carp::Ensure::is_a_
userType
must be defined. When checking a value for being a userType, the sub is
called with a single argument being a reference(!) to the value it should
check. This minimizes copying. The sub must return false if the referenced
value is not of the desired type and a true value otherwise. See is_a
for an
example.
The terminal symbols have the following meaning:
HASH
The value is a reference(!)
to a hash with arbitrary keys and values. Use this
if you don't want to check the hash content.
ARRAY
The value is a reference(!)
to an array with arbitrary content. Use this if you
don't want to check the array content.
CODE
The value is a reference to some code. This may be an anonymous or a named sub.
GLOB
The value is a GLOB.
undefined
Only the undefined value is permitted. Often used as one part of an alternative. Missing optional arguments of a sub are undefined, also.
defined
The value only needs to be defined.
anything
Actually not a test since anything is permitted.
string
An arbitrary string.
word
A string matching /w+/
.
empty
An empty string.
integer
An integer.
float
An floating point number.
boolean
A boolean. Actually every scalar is a boolean in Perl, so this is more a description of how a certain value is used.
regex
A string which compiles cleanly as a regular expression. The regex
is
applied to an empty string so any parentheses in the regex
will probably
don't result in anything useful.
Note, that nothing prevents the regex
from executing arbitrary code if you
manage to include this somehow. The results are completly undefined.
The precedence of the operators is as indicated by the grammar. Because most operators are prefix operators there is not much room for ambiguity anyway. However, the grammar for alternatives opens some traps. In particular the current grammar means, that it is not possible to have
A type description \type1|type2
would be parsed as an alternative between
\type1
and type2
instead of a reference to either type1
or type2
.
Use \type1|\type2
instead.
A type description @type1|@type2
is indeed not allowed by the grammar.
Probably you're thinking of @type1|type2
anyway which describes an array
consisting of type1
and/or type2
values.
If you want to describe arrays consisting of exactly one or another type use an
additional reference for your value and try \@type1|\@type2
.
Similarly %typeK=
>@typeV1|typeV2
is not allowed by the grammar. It would
not make sense anyway because a list can not be the value of a hash key.
However, %typeK=
>\@typeV1|\@typeV2
is possible and describes a hash
mapping typeK
values to references to arrays consisting of either typeV1
or typeV2
elements.
A type description \@type1|type2
describes a reference to an array of
type1
elements or a type2
value. It is NOT a reference to an array
consisting of type1
and/or type2
elements.
Even worse \%typeK1|typeK2=
>typeV
can't be parsed at all because the
alternative is evaluated before the hash designator.
Note, that you can always define your own test functions which may break down
complex types to simple names. With the is_a
function this is usually done
with a few key strokes.
As noted above the lack of parentheses in the grammar makes some complex constructions impossible. However, introducing parentheses would make a more complex parser necessary. After all user defined types may be used for simulating parentheses.
If parentheses, brackets and braces would be added to the grammar, the following changed productions would be probably best:
'(
' alternative ')
| reference | ...
'\
' simple | '[
' alternative ']
' | '{
' alternative '=
>' alternative '}
' | class | ...
Furthermore it would be nice to have
user | '/
' match '/
' | number '..
' number
a valid Perl regex
/^[-+]?\d+(\.\d*)([Ee][-+]\d+)?$/
so you can define an anonymous type for a string matching a regex or for a number being inside a range. But given the rich structure of Perl regexes at least the match would require a real parser.
There is the Usage package which has a similar functionality. However, it dates 1996 and seems not be maintained since then. Unfortunately it is not as flexible as this module and is still a bit buggy.
Stefan Merten <smerten@oekonux.de>
The idea for the code implementing the DEBUG feature was taken from the Carp::Assert manpage by Michael G. Schwern <schwern@pobox.com>.
Carp
This program is licensed under the terms of the GPL. See
http://www.gnu.org/licenses/gpl.txt
See
http://www.merten-home.de/FreeSoftware/Carp_Ensure/