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

Using Moose, I've got a subtype 'AFile' which coerces a String to a Path::Class::File. I'm trying to create a subtype of 'AFile' called 'ExistingFile' where I'm trying to verify that the file exists. When I create an object that has an attribute of type 'ExistingFile', if I pass in a Path::Class::File, everything "seems" to work, but if I pass in a String, things fail. It appears that in the event the attribute is being coerced, the "where" clause in the 'ExistingFile' subtype is being skipped. Here's some code to illustrate what I'm seeing:

#! /usr/bin/env perl use strict; use warnings; use 5.10.0; package Foo; use Moose; use Moose::Util::TypeConstraints; use namespace::autoclean; use Path::Class qw / file /; subtype 'AFile' => as 'Path::Class::File' => message { 'Item is not a Path::Class::File' }; coerce 'AFile' => from 'Str' => via { file($_) }; subtype 'ExistingFile' => as 'AFile' => where { say "In subtype where clause: $_[0]"; -f $_[0] }, message { "$_[0] does not exist" }; has file => ( isa => 'ExistingFile', is => 'ro', required => 1 ); __PACKAGE__->meta->make_immutable; package main; use Path::Class qw / file /; say $0; eval { my $foo = Foo->new(file => file($0)); }; say ":::".$@.":::"; eval { my $foo = Foo->new(file => $0); }; say ":::".$@.":::";

And the output when I run the above:

subtype_madness.pl In subtype where clause: subtype_madness.pl :::::: :::Attribute (file) does not pass the type constraint because: subtype +_madness.pl does not exist at constructor Foo::new (defined at subtyp +e_madness.pl line 29) line 34 Foo::new('Foo', 'file', 'subtype_madness.pl') called at subtype_ma +dness.pl line 36 eval {...} at subtype_madness.pl line 36 :::

While this example has all the code in one place, in my "real world" the 'AFile' subtype is off in a library somewhere.

I could work around this by creating 'ExistingFile' as a subtype of Path::Class::File, but it *seems* like I should be able to subtype 'AFile'. Thoughts?

Replies are listed 'Best First'.
Re: Moose: Problems subtyping a coerced subtype
by duelafn (Parson) on Jan 21, 2015 at 17:43 UTC

    Moose won't do coersion unless you request it, so, at the least, you would need:

    has file => ( isa => 'ExistingFile', is => 'ro', required => 1, coerce + => 1 );

    However, that doesn't work for me either, complaining:

    You cannot coerce an attribute (file) unless its type (ExistingFile) h +as a coercion at /tmp/test.pl line 26

    Apparently, coersion is not inherited by subtypes by design, so you will need to duplicate the coersion.

    subtype 'ExistingFile' => as 'AFile' => where { say "In subtype where clause: $_[0]"; -f $_[0] }, message { "$_[0] does not exist" }; coerce 'ExistingFile' => from 'Str' => via { file($_) }; has file => ( isa => 'ExistingFile', is => 'ro', required => 1, coerce + => 1 );

    Note: You can reduce the code duplicationg using:

    for my $ftype (qw/ AFile ExistingFile /) { coerce $ftype => from 'Str' => via { file($_) }; }

    Good Day,
        Dean

      Thanks duelafn, and especially thanks for the link. I didn't know that coercions were not inherited. Ah well. Since the subtypes are in different files, I'll either need to duplicate code or make other arrangements.

      Thanks again, I appreciate you taking time to help.

        You shouldn't need to duplicate code; as via can take a code ref in addition to a block, define an actual subroutine in the parent class and grab its reference via fully qualified package; or use introspection to traverse the coercion map (Moose::Meta::TypeCoercion). The former will be more obvious from casual inspection, and the latter would be more robustly maintainable.

        Neither is particularly elegant, though.


        #11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.