Skip Menu |

This queue is for tickets about the Type-Tiny CPAN distribution.

Report information
The Basics
Id: 88291
Status: resolved
Priority: 0/
Queue: Type-Tiny

People
Owner: Nobody in particular
Requestors: djerius [...] cpan.org
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: 0.024
Fixed in: (no value)



Subject: Grouped alternatives
I'm trying to validate a parameter list which could look either like this: InstanceOf['Foo'] or this ArrayRef, ArrayRef, ArrayRef, ArrayRef Intuitively it feels like it should look something like this: compile( InstanceOf['Foo'] | ( ArrayRef, ArrayRef, ArrayRef, ArrayRef ) ); But that of course doesn't work. My attempt at converting into a valid syntax was this: compile( InstanceOf['Foo'] | [ ArrayRef, ArrayRef, ArrayRef, ArrayRef ] ); and that doesn't work either, and even if it did, this wouldn't: compile( [ InstanceOf['Foo'], ArrayRef ] | [ ArrayRef, ArrayRef, ArrayRef, ArrayRef ] ); If it doesn't already exist, it be very useful if there were a means of gathering types into a "group", so that something like this compile( TypeGroup[ InstanceOf['Foo'], ArrayRef ] | TypeGroup[ ArrayRef, ArrayRef, ArrayRef, ArrayRef ] ); would be possible. Thanks, Diab
This is close... use Types::Standard -types; use Type::Params; my $check = compile( InstanceOf['Foo'] | Tuple[ (ArrayRef) x 4 ] ); $check->( bless({}, 'Foo') ); $check->([ [1 .. 10], ['a' .. 'z'], [0 .. 9], ['A' .. 'Z'], ]); Though note that you've got to use an arrayref of arrayrefs for the second case because slurpy cannot participate in a union. The other alternative would be to compile two different checks, and then switch between them based on the length of @_, like this: use Types::Standard -types; use Type::Params; my $check1 = compile( InstanceOf['Foo'] ); my $check2 = compile( (ArrayRef) x 4 ); my $check = sub { @_ == 1 ? goto($check1) : goto($check2) }; $check->( bless({}, 'Foo') ); $check->( [1 .. 10], ['a' .. 'z'], [0 .. 9], ['A' .. 'Z'], ); I'll leave this ticket open, because I'm considering writing something to make the pattern above a little easier to do.
This is included in 0.025_02; I'll close the ticket once it's in a stable release, but feel free to re-open it if you don't think it's a sensible solution.
On Mon Sep 02 16:32:57 2013, TOBYINK wrote: Show quoted text
> This is included in 0.025_02; I'll close the ticket once it's in a > stable release, but feel free to re-open it if you don't think it's a > sensible solution.
To be honest, I was thinking more along the lines of alternates in regular expressions, which would allow nesting groups (which I don't think is possible with the multisig approach): TypeA | TypeGroup( TypeB, TypeGroup( TypeC | TypeD ) ) but this may be beyond the scope of what you're trying to accomplish. (Although it does look like Type::Parser does something similar, if I understand it correctly). I hadn't thought of the point you made in the multisig documentation, namely that there's no means of returning which alternative had matched. If one could name positional parameters (similar to named captures in regular expressions), the validation routine could return a hash rather than a list of values.
For simple cases you can already use a union. For example, if you want to accept an Int, followed by either an ArrayRef or HashRef, you can do: state $check = compile(Int, HashRef|ArrayRef); However, this has limitations. For example, say we write a function that accepts either Int-then-ArrayRef or Str-then-HashRef; the obvious way is: sub get_from { state $check = compile(Int|Str, ArrayRef|HashRef); my ($needle, $haystack) = $check->(@_); is_HashRef($haystack) ? $haystack->{$needle} : is_ArrayRef($haystack) ? $haystack->[$needle] : die; } So we can do: get_from(0, \@arr); # get index 0 from array get_from('foo', \%h); # get key 'foo' from hash which works, but is a looser check than we wanted - it will accept: get_from('foo', \@arr); # !!! It may be possible to do something like: my $first_alt = Tuple[Int, ArrayRef]; my $second_alt = Tuple[Str, HashRef]; state $check = compile slurpy($first_alt|$second_alt); Not sure how well making a union type slurpy works in practice. I think it ought to work in this particular case. multisig() is more generic. You can just give each alternative whole signature as an arrayref: state $check = multisig( [ Int, ArrayRef ], [ Str, HashRef ], ); Although not yet documented, it's also possible to provide coderefs as alternatives. Let's say that we wanted to accept a third alternative where the first parameter is a method name and the second parameter is an object has that method: sub get_from { state $check = multisig( [ Int, ArrayRef ], [ Str, HashRef ], sub { my ($meth, $obj); die unless is_Object($obj); die unless $obj->can($meth); return ($meth, $obj); }, ); my ($needle, $haystack) = $check->(@_); is_HashRef($haystack) ? $haystack->{$needle} : is_ArrayRef($haystack) ? $haystack->[$needle] : is_Object($haystack) ? $haystack->$needle : die; } That all having been said, Type::Params is not the be all and end all of parameter processing. It's supposed to handle most common situations, but it's not always going to be enough. When it's not, hopefully Type::Tiny can still help out with manual parameter processing. (Using check/assert/coerce methods.) See also https://metacpan.org/module/Type::Tiny::Manual::Params
Fixed (kinda) in 0.026.