views:

490

answers:

3

Building off Does Perl have an enumeration type?, how can I perform dynamic type checking (or static type checking if use strict is able to do so) that my subroutine argument is getting the right type of enum?

package Phone::Type;

use constant {
    HOME => 'Home',
    WORK => 'Work',
};

package main;

sub fun
{
    my ($my_phone_type_enum) = @_;
    # How to check my_phone_type_enum, is either Phone::Type->HOME or Phone::Type->WORK or ... but not 'Dog' or 'Cat'?
}

fun(Phone::Type->HOME); # valid
fun(Phone::Type->WORK); # valid
fun('DOG');             # run-time or compile time error
+4  A: 

Here is one way:

#!/usr/bin/perl

package Phone::Type;

use strict;
use warnings;

use constant {
    HOME => 'Home',
    WORK => 'Work',
};

package main;

use strict;
use warnings;

sub fun {
    my ($phone_type) = @_;
    Phone::Type->can( $phone_type )
        or die "'$phone_type' is not valid\n";
}

fun('HOME'); # valid
fun('WORK'); # valid
fun('DOG');  # run-time or compile time error
__END__

C:\Temp> dfg
'DOG' is not valid
Sinan Ünür
But there you are passing the name of the type, not the value. That's not what was asked for.
ysth
@ysth I cheated ;-)
Sinan Ünür
By usinguse constant { HOME => 'HOME', WORK => 'WORK',};It work very fine with this good looking syntax :fun(Phone::Type::HOME);However, when I use the Readonly.Readonly our $HOME => 'HOME';Readonly our $WORK => 'WORK';and I try to call with the following syntax :fun($Phone::Type::HOME);I get 'HOME' is not validHow can I make it workable too for Readonly?
Yan Cheng CHEOK
p/s Some note to be added, I wish to make it workable too for Readonly, is because rest of my legacy code are using Readonly. For consistency purpose.
Yan Cheng CHEOK
+2  A: 

I would suggest that you use Readonly (as suggested in the referenced question) rather than constant. I'd suggest on of two possible approaches (depending on if you are using Perl 5.10 or 5.8).

Initially, the same code:

use strict;
use warnings;
use Readonly;

Readonly my @phone_types = qw/HOME WORK/;

Perl 5.10:

sub fun
{
   my $type = shift;
   die "Invalid phone type: $type" unless $type ~~ @phone_types;
   # ...
}

Perl 5.8:

sub fun
{
   my $type = shift;
   die "Invalid phone type: $type" unless grep { $_ eq $type} @phone_types;
   # ...
}

There is a module on CPAN which will allow you to have a great deal of control over argument types and values but I can't for the life of me remember it. Perhaps someone else can.

Nic Gibson
There's at least Params::Validate and the newer MooseX::Types. They do greatly different things, though.
tsee
Params::Validate was the one I was trying to remember. Thanks :) Probably more than a little excessive for this sort of usage though.
Nic Gibson
+2  A: 
package Phone::Type;

my $types;
BEGIN {
    $types = {
        HOME => 'Home',
        WORK => 'Work',
    };
}
use constant $types;

sub is_phone_type {
    my ($type) = @_;
    return exists $types->{$type};
}

package main;
use Carp ();

sub fun
{
    my ($my_phone_type_enum) = @_;
    Phone::Type::is_phone_type( $my_phone_type_enum)
        or Carp::croak "Invalid type $my_phone_type_enum";
}

fun(Phone::Type->HOME); # valid
fun(Phone::Type->WORK); # valid
fun('DOG');             # run-time or compile time error

(The BEGIN is necessary to set $types at compile time so that it's available to the use statement.)

It's fairly common in Perl to be relaxed about things like this; to just assume that functions are passed numbers where you expect numbers, etc. But if you want to do this kind of validation, you may be interested in Params::Validate.

ysth
I'd put an eval in is_phone_type in case it gets passed garbage that isn't a hash ref. The answer still comes back as false.
brian d foy
@brian d foy: ? is_phone_type stringizes its input, not dereferences it.
ysth