Skip Menu |

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

Report information
The Basics
Id: 131243
Status: rejected
Priority: 0/
Queue: Type-Tiny

People
Owner: perl [...] toby.ink
Requestors: haukex [...] zero-g.net
Cc:
AdminCc:

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



Subject: Ability to use "inlined" only instead of "constraint"
Hi, A quick test with a custom type appears to tell me that I can't just provide "inlined", I have to provide a "constraint" too, even though it seems like it would be fairly easy to use "inlined" to generate the "constraint", wouldn't it? Similarly, providing both "inline_generator" and "constraint_generator" feels like code duplication. So in other words it would be great if I could provide only "inlined" and "inline_generator" without providing "constraint" and "constraint_generator". (Taking that a step further, wouldn't it be possible for a single sub to generate everything? It could be passed an arrayref or undef to differentiate between "BaseType[]" and "BaseType". Or am I missing something?) Thanks and Happy Holidays, -- Hauke D
Hi, The attached file shows a concrete example of what I'm referring to, there's a lot of repetition there and I'm not sure how to reduce it other than maybe write a generator for the generators :-) Thanks, Best, -- Hauke D
Subject: typetinytest.pl
#!/usr/bin/env perl use warnings; use strict; use Type::Tiny::XS (); # for speed use Ref::Util::XS (); # for speed use Type::Tiny; BEGIN { package MyPgTypes { use Type::Library -base; use Types::Standard qw/ Optional LaxNum /; use Types::Common::Numeric qw/ PositiveInt PositiveOrZeroInt /; use Type::Params qw/ compile /; # ### https://www.postgresql.org/docs/12/datatype-numeric.html#DATATYPE-NUMERIC-DECIMAL # The precision of a numeric is the total count of significant digits in the whole number, # that is, the number of digits to both sides of the decimal point. The scale of a numeric # is the count of decimal digits in the fractional part, to the right of the decimal point. # So the number 23.5141 has a precision of 6 and a scale of 4. Integers can be considered # to have a scale of zero. # NUMERIC(precision, scale) - The precision must be positive, the scale zero or positive. # NUMERIC(precision) - selects a scale of 0. # NUMERIC - creates a column in which numeric values of any precision and scale can be stored, # up to the implementation limit on precision. # The maximum allowed precision when explicitly specified in the type declaration is 1000. # If the scale of a value to be stored is greater than the declared scale of the column, # the system will round the value to the specified number of fractional digits. Then, if # the number of digits to the left of the decimal point exceeds the declared precision # minus the declared scale, an error is raised. # ### my $checker = compile( PositiveInt, Optional[PositiveOrZeroInt] ); my $num = __PACKAGE__->add_type( name => 'PgNumeric', parent => LaxNum, # Must use a coderef as the constraint for now because https://rt.cpan.org/Ticket/Display.html?id=131238 constraint => sub { /\A(?:-?\d+(?:\.\d+)?|(?i)NaN)\z/ }, inlined => sub { my ($self, $varname) = @_; return ( undef, sprintf(q{ %s =~ /\A(?:-?\d+(?:\.\d+)?|(?i)NaN)\z/ }, $varname) ); }, constraint_generator => sub { my ($precision, $scale) = $checker->(@_); die "precision too large, max is 1000" if $precision > 1000; die "precision must be greater than scale" unless $precision > $scale; my $int = $precision - $scale; my $re = $scale ? qr/\A(?:-?\d{1,$int}(?:\.\d{1,$scale})?|(?i)NaN)\z/ : qr/\A(?:-?\d{1,$int}|(?i)NaN)\z/; return sub { $_ =~ $re }; }, inline_generator => sub { my ($precision, $scale) = $checker->(@_); die "precision too large, max is 1000" if $precision > 1000; die "precision must be greater than scale" unless $precision > $scale; my $int = $precision - $scale; my $re = $scale ? qq{/\\A(?:-?\\d{1,$int}(?:\.\\d{1,$scale})?|(?i)NaN)\\z/} : qq{/\\A(?:-?\\d{1,$int}|(?i)NaN)\\z/}; return sub { my ($self, $varname) = @_; return ( undef, "$varname =~ $re" ); }; }, ); __PACKAGE__->make_immutable; } MyPgTypes->import(':all'); } use Test::More; use Test::TypeTiny; should_pass($_, PgNumeric) for "1","1.1","12345.67890","-1","-1.1","-12345.67890","NaN","nan"; should_fail($_, PgNumeric) for ".1","1.","","x","1.x","-.1","-1.","-","-x","-1.x"; should_pass($_, PgNumeric[10,5]) for "1","1.1","12345.67890","-1","-1.1","-12345.67890","NaN","nan"; should_fail($_, PgNumeric[10,5]) for ".1","1.","","x","1.x","-.1","-1.","-","-x","-1.x", "123456.67890","12345.678901","-123456.67890","-12345.678901"; my $t = PgNumeric[10,5]; is $t->display_name, 'PgNumeric[10,5]', 'display_name is as expected'; done_testing;
There would be a certain logic to auto-generating a constraint from inlined, yeah. You can actually to the inverse though, if you provide the constraint as a string of Perl code. This won't help for parameterization though.
I'm leaning towards not implementing this. There's a key difference between what the inlining coderef and the constraint coderef do. The constraint coderef *isn't* responsible for checking values against the parent type constraint. The inlining coderef *is* responsible for generating code to check against the parent type constraint. If Type::Tiny used the inlined code to build the constraint coderef, it would end up testing values against the parent type twice. (Putting the current limitation for parameterizable types aside...) You can already generate "inlined" by providing "constraint" as a string, so there's no need to also be able to generate "constraint" from "inlined".
Hi, On Sun Dec 29 16:04:02 2019, TOBYINK wrote: Show quoted text
> I'm leaning towards not implementing this. > > There's a key difference between what the inlining coderef and the > constraint coderef do. The constraint coderef *isn't* responsible for > checking values against the parent type constraint. The inlining > coderef *is* responsible for generating code to check against the > parent type constraint. If Type::Tiny used the inlined code to build > the constraint coderef, it would end up testing values against the > parent type twice.
That makes sense, thanks for looking at this, and yes, I see that it doesn't really make sense to implement it the way I suggested. I just spent some time re-reading Type::Tiny::Manual::Libraries, ::Manual::Optimization, and ::Tiny - unless there's some other docs I should be reading about this topic, I have some suggestions for clarifications in the documentation: - So I get that constraint_generator isn't supposed to return a string, only a coderef or Type::Tiny object (although the latter is only mentioned in ::Tiny, not ::Manual::Libraries). Does that mean that if I want the parameterized type to have a string "constraint" so that it can be optimized, I need to return a Type::Tiny object? Might be worth a mention or even a code example - for example, is "$Type::Tiny::parameterize_type->where('...')" correct? - Taking that thought a step further, would it make sense to extend constraint_generator such that if it returns a string, that means $Type::Tiny::parameterize_type->where($string), or is that too much magic? - "Parameterizable Types" in ::Manual::Libraries seems to "recommend" inline_generator in addition to constraint_generator, but it seems to me that the recommendation might be the same as "constraint" vs. "inlined", i.e. the latter is only really useful if the chain of "constraint"s could be simplified, no? Either that, or perhaps mention why it's recommended? - I think it might be useful to mention somewhere that "inlined" should be provided in addition to "constraint", not as an alternative to it, and the same for inline_generator vs. constraint_generator. Thanks, Best, -- Hauke D
Extending constraint_generator to be able to return a string seems like it might be possible.
Hi, On Mon Dec 30 18:24:45 2019, TOBYINK wrote: Show quoted text
> Extending constraint_generator to be able to return a string seems > like it might be possible.
That sounds good, thanks! I've attached what I've come up with so far, it seems to work well now - I did end up writing a separate sub to generate the regexes. I also realized that "constraint_generator" can return a type with a "constraint" and "inlined". Strangely, whether the parametrized version of the type is inlined doesn't seem to depend on "inline_generator". For example, the "inline_check" for "PgNumeric[3,3]" should just be do { defined($foo) && $foo=~/\A(?!-?\.?\z)(?:-?0*(?:\.\d{0,3})?|(?i)NaN)\z/ } and in the attached code it is, but if I remove "inlined" from the parametrized type, then the above becomes: ((do { defined($foo) && $foo=~/\A(?!-?\.?\z)(?:-?\d{0,}(?:\.\d{0,})?|(?i)NaN)\z/ }) && (do { local $_ = $foo; /\A(?!-?\.?\z)(?:-?0*(?:\.\d{0,3})?|(?i)NaN)\z/ })) i.e. it's still checking the parent type, no matter whether I have "inline_generator" present or not. Misunderstanding on my part or a bug? (It's in the comments in the attached file.) Would you be able to estimate when you might release the fix for #131238? I'm using a patched version at the moment. Thanks, Best, -- Hauke D
Subject: typetinytest2.pl
#!/usr/bin/env perl use warnings; use strict; package MyPgTypes { use Type::Library -base; use Types::Standard qw/ Optional Defined /; use Types::Common::Numeric qw/ PositiveInt PositiveOrZeroInt /; use Type::Params qw/ compile /; use feature 'state'; sub _gen_pg_num_re { state $check = compile( PositiveInt, Optional[PositiveOrZeroInt] ); my ($scl, $int) = ('', ''); if (@_) { my ($prec, $scale) = $check->(@_); $scale //= 0; die "precision too large, max is 1000\n" if $prec > 1000; die "precision must be greater than or equal to scale\n" unless $prec >= $scale; ($scl, $int) = ( $scale || undef, ($prec-$scale) || undef ); } return '/\A(?!-?\.?\z)(?:-?' . ( defined $int ? '\d{0,'.$int.'}' : '0*' ) . '(?:\.' . ( defined $scl ? '\d{0,'.$scl.'}' : '0*' ) . ')?|(?i)NaN)\z/'; } my $num = __PACKAGE__->add_type( parent => Defined, name => 'PgNumeric', constraint => _gen_pg_num_re(), inlined => sub { "defined($_[1]) && $_[1]=~"._gen_pg_num_re() }, constraint_generator => sub { my @args = @_; $Type::Tiny::parameterize_type->create_child_type( display_name => $Type::Tiny::parameterize_type->name.'['.join(',', grep {$_} @args).']', constraint => _gen_pg_num_re(@args), inlined => sub { "defined($_[1]) && $_[1]=~"._gen_pg_num_re(@args) } ); }, # If you comment out the "inlined" in "create_child_type" above, # it doesn't look like this "inline_generator" is making a difference: inline_generator => sub { my @args = @_; return sub { "defined($_[1]) && $_[1]=~"._gen_pg_num_re(@args) }; } ); __PACKAGE__->make_immutable; }
You're returning a Type::Tiny object from the constraint_generator, so the inline_generator is totally ignored. https://metacpan.org/pod/Type::Tiny#Attributes-related-to-parameterizable-and-parameterized-types Show quoted text
> The constraint generator should generate and return a new constraint > coderef based on the parameters. Alternatively, the constraint generator > can return a fully-formed Type::Tiny object, in which case the > name_generator, inline_generator, and coercion_generator attributes > documented below are ignored.
On Sat Jan 11 16:54:41 2020, TOBYINK wrote: Show quoted text
> You're returning a Type::Tiny object from the constraint_generator, so > the inline_generator is totally ignored.
Duh, right, thank you :-) Best, -- Hauke D
Basically the interface that Type::Tiny borrowed from Moose::Meta::TypeConstraint was for parameterizable types to have a `constraint_generator` and optionally an `inline_generator`. For some types, I found adding a `name_generator` to be useful. And later `coercion_generator` proved useful too. After that, I figured rather than keep adding generators, I'd provide the option for `constraint_generator` to just do all the work. Anyway, for reasons stated earlier, rejecting this as an issue. Generating `constraint` automatically from `inlined` isn't always feasible because `inlined` is responsible for inlining parent type checks too, but `constraint` is not responsible for checking parent type checks. If you don't want to provide them both, it is already possible to just provide `constraint` as a string.