in reply to Re: Moose type question
in thread Moose type question

Elsewhere, you posted http://article.gmane.org/gmane.comp.lang.perl.moose/1697

which has two possible values, None, meaning no value, and Some(`a) meaning a value of the type `a

That sounds like a union, so I tried it. Coercion works! ...but constraints don't?! Am I missing something?

Simple demonstration:

$ perl -Moose -e'has a=>(is=>"rw",isa=>"Undef",coerce=>1); __PACKAGE__ +->new(a=>"abc")' Attribute (a) does not pass the type constraint because: Validation fa +iled for 'Undef' failed with value abc at [snip backtrace] $ perl -Moose -e'has a=>(is=>"rw",isa=>"Undef|Undef",coerce=>1); __PAC +KAGE__->new(a=>"abc")' $ perl -Moose -e'has a=>(is=>"rw",isa=>"Undef|Undef",coerce=>0); __PAC +KAGE__->new(a=>"abc")' Attribute (a) does not pass the type constraint because: Validation fa +iled for 'Undef|Undef' failed with value abc at [snip backtrace]

Full demonstration:

BEGIN { package Class; use strict; use warnings; use Moose; has val => ( is => 'rw', isa => 'Num', required => 1, ); use Moose::Util::TypeConstraints; coerce __PACKAGE__ => from 'Num' => via { __PACKAGE__->new(val => $_) }; } BEGIN { package Container; use strict; use warnings; use Moose; has always => ( is => 'rw', isa => 'Class', coerce => 1, default => sub { Class->new(val => 0) }, ); has union => ( is => 'rw', isa => 'Undef|Class', coerce => 1, default => undef, ); } use strict; use warnings; use Test::More tests => 30; use Scalar::Util qw( blessed ); sub ta { $_[0] && blessed($_[0]->always) && blessed($_[0]->always) eq +'Class' } sub tu { $_[0] && ( !$_[0]->union || blessed($_[0]->union) && blessed( +$_[0]->union) eq 'Class' ) } my $always_fail = qr/^Attribute \(always\) does not pass the type cons +traint/; my $union_fail = qr/^Attribute \(union\) does not pass the type const +raint/; my $o = Class->new(val => 123); my $c; $c = eval { Container->new() }; is($@, ''); + ok(ta($c)); $c = eval { Container->new(always => $o) }; is($@, ''); + ok(ta($c)); $c = eval { Container->new(always => 123) }; is($@, ''); + ok(ta($c)); $c = eval { Container->new(always => "abc") }; like($@, $always_fail) +; ok(!$c); $c = eval { Container->new(union => undef) }; is($@, ''); + ok(tu($c)); $c = eval { Container->new(union => $o) }; is($@, ''); + ok(tu($c)); $c = eval { Container->new(union => 123) }; is($@, ''); + ok(tu($c)); $c = eval { Container->new(union => "abc") }; like($@, $union_fail); + ok(!$c); $c = Container->new(); eval { $c->always($o) }; is($@, ''); ok(tu($c)); eval { $c->always(123) }; is($@, ''); ok(tu($c)); eval { $c->always("abc") }; like($@, $always_fail); ok(tu($c)); eval { $c->union(undef) }; is($@, ''); ok(tu($c)); eval { $c->union($o) }; is($@, ''); ok(tu($c)); eval { $c->union(123) }; is($@, ''); ok(tu($c)); eval { $c->union("abc") }; like($@, $union_fail); ok(tu($c));
$ perl a.pl 1..30 ok 1 ok 2 ok 3 ok 4 ok 5 ok 6 ok 7 ok 8 ok 9 ok 10 ok 11 ok 12 ok 13 ok 14 not ok 15 # Failed test at a.pl line 71. # '' # doesn't match '(?-xism:^Attribute \(union\) does not pass the ty +pe constraint)' not ok 16 # Failed test at a.pl line 71. ok 17 ok 18 ok 19 ok 20 ok 21 ok 22 ok 23 ok 24 ok 25 ok 26 ok 27 ok 28 not ok 29 # Failed test at a.pl line 82. # '' # doesn't match '(?-xism:^Attribute \(union\) does not pass the ty +pe constraint)' ok 30 # Looks like you failed 3 tests of 30.

Replies are listed 'Best First'.
Re^3: Moose type question
by stvn (Monsignor) on Jun 15, 2010 at 14:49 UTC
    That sounds like a union, so I tried it. Coercion works! ...but constraints don't?! Am I missing something?

    It is not a union, it is an algebraic data type, there is a HUGE difference in terms of type safety and soundness.

    Your second "Simple demonstration" seems to be a bug, I have brought it up in #moose-dev to discuss.

    UPDATE:
    Actually we figured out what is happening there, it is a fall through on an edge case. So if you look at this:

    perl -Moose -e'has a=>(is=>"rw",isa=>"Undef|Undef",coerce=>1); my $x = + __PACKAGE__->new(a=>"abc"); print $x->a'
    You will get Use of uninitialized value in print at -e line 3., which is happening because when Moose::Meta::TypeCoercion::Union attempts to coerce and doesnt find a proper coercion it returns 'undef'. Which in most cases would cause a type-check failure, but here actually passes the type check just fine.

    Funny, because this perfectly illustrates why I really don't like undef. In this case we are trying to use it as a "no value", but your test case is using it as a valid value itself. The two ways of using it do not mesh well at all.

    UPDATE(2):
    See also Semipredicate Problem.

    -stvn

      It is not a union, it is an algebraic data type

      I have no idea what that means. You described a type that could take one of two types of values, and that what ::Union does. I figured it would be a solution that falls within your mental model, and thus likely to work and to get promoted.

      Your second "Simple demonstration" seems to be a bug

      Filed as CPAN RT#58411

      Funny, because this perfectly illustrates why I really don't like undef.

      The problem has nothing to do with undef. The problem is sending control data in band. A value was reserved to mean "no coercion occurred". If the function had used zero instead of undef, it would suffer from the same bug.

      Undef is only a problem if you think it's ok for you and only you to use undef despite a long and continuing history of usefully using undef in Perl.

        I have no idea what that means.

        Algebraic Data types are mostly from functional languages. In spirit they are similar to C unions, but in practice they are very different. They are also type-safe meaning they fit into the theoretical end of the type system whereas in C the "type" is really more a way of telling the compiler how much space to allocate. If you have ever played with Haskell or OCaml you have likely encountered them, but they are most definitely not the same thing as a union (C style or Moose style).

        Filed as CPAN RT#58411

        Yes, thank you very much, we have been discussing the best way to solve this.

        The problem has nothing to do with undef.

        You are correct, it is the Semi-Predicate problem, which we clearly stepped into with this implementation.

        Undef is only a problem if you think it's ok for you and only you to use undef despite a long and continuing history of usefully using undef in Perl.

        Right, thats my problem though. That long and continuing history of use is inconsistent and at times contradictory. Just think of all the possible permutations of return;,  return undef; and  return (); and what each of them means in scalar and list context. Now there be dragons.

        -stvn