|
Packit |
42cdad |
package Specio::Coercion;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
use strict;
|
|
Packit |
42cdad |
use warnings;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
our $VERSION = '0.42';
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
use Specio::OO;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
use Role::Tiny::With;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
use Specio::Role::Inlinable;
|
|
Packit |
42cdad |
with 'Specio::Role::Inlinable';
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
{
|
|
Packit |
42cdad |
## no critic (Subroutines::ProtectPrivateSubs)
|
|
Packit |
42cdad |
my $role_attrs = Specio::Role::Inlinable::_attrs();
|
|
Packit |
42cdad |
## use critic
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
my $attrs = {
|
|
Packit |
42cdad |
%{$role_attrs},
|
|
Packit |
42cdad |
from => {
|
|
Packit |
42cdad |
does => 'Specio::Constraint::Role::Interface',
|
|
Packit |
42cdad |
required => 1,
|
|
Packit |
42cdad |
},
|
|
Packit |
42cdad |
to => {
|
|
Packit |
42cdad |
does => 'Specio::Constraint::Role::Interface',
|
|
Packit |
42cdad |
required => 1,
|
|
Packit |
42cdad |
weak_ref => 1,
|
|
Packit |
42cdad |
},
|
|
Packit |
42cdad |
_coercion => {
|
|
Packit |
42cdad |
isa => 'CodeRef',
|
|
Packit |
42cdad |
predicate => '_has_coercion',
|
|
Packit |
42cdad |
init_arg => 'coercion',
|
|
Packit |
42cdad |
},
|
|
Packit |
42cdad |
_optimized_coercion => {
|
|
Packit |
42cdad |
isa => 'CodeRef',
|
|
Packit |
42cdad |
init_arg => undef,
|
|
Packit |
42cdad |
lazy => 1,
|
|
Packit |
42cdad |
builder => '_build_optimized_coercion',
|
|
Packit |
42cdad |
},
|
|
Packit |
42cdad |
};
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
|
|
Packit |
42cdad |
sub _attrs {
|
|
Packit |
42cdad |
return $attrs;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub BUILD {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
die
|
|
Packit |
42cdad |
'A type coercion should have either a coercion or inline_generator parameter, not both'
|
|
Packit |
42cdad |
if $self->_has_coercion && $self->_has_inline_generator;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
die
|
|
Packit |
42cdad |
'A type coercion must have either a coercion or inline_generator parameter'
|
|
Packit |
42cdad |
unless $self->_has_coercion || $self->_has_inline_generator;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
return;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub coerce {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
my $value = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
return $self->_optimized_coercion->($value);
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub inline_coercion {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
return $self->_inline_generator->( $self, @_ );
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub _build_optimized_coercion {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
if ( $self->_has_inline_generator ) {
|
|
Packit |
42cdad |
return $self->_generated_inline_sub;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
else {
|
|
Packit |
42cdad |
return $self->_coercion;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub can_be_inlined {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
return $self->_has_inline_generator && $self->from->can_be_inlined;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub _build_description {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
my $from_name
|
|
Packit |
42cdad |
= defined $self->from->name ? $self->from->name : 'anonymous type';
|
|
Packit |
42cdad |
my $to_name
|
|
Packit |
42cdad |
= defined $self->to->name ? $self->to->name : 'anonymous type';
|
|
Packit |
42cdad |
my $desc = "coercion from $from_name to $to_name";
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
$desc .= q{ } . $self->declared_at->description;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
return $desc;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
sub clone_with_new_to {
|
|
Packit |
42cdad |
my $self = shift;
|
|
Packit |
42cdad |
my $new_to = shift;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
my $from = $self->from;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
local $self->{from} = undef;
|
|
Packit |
42cdad |
local $self->{to} = undef;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
my $clone = $self->clone;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
$clone->{from} = $from;
|
|
Packit |
42cdad |
$clone->{to} = $new_to;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
return $clone;
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
__PACKAGE__->_ooify;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
1;
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
# ABSTRACT: A class representing a coercion from one type to another
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
__END__
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=pod
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=encoding UTF-8
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 NAME
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Specio::Coercion - A class representing a coercion from one type to another
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 VERSION
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
version 0.42
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 SYNOPSIS
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
my $coercion = $type->coercion_from_type('Int');
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
my $new_value = $coercion->coerce_value(42);
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
if ( $coercion->can_be_inlined() ) {
|
|
Packit |
42cdad |
my $code = $coercion->inline_coercion('$_[0]');
|
|
Packit |
42cdad |
}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 DESCRIPTION
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This class represents a coercion from one type to another. Internally, a
|
|
Packit |
42cdad |
coercion is a piece of code that takes a value of one type returns a new value
|
|
Packit |
42cdad |
of a new type. For example, a coercion from c<Num> to C<Int> might round a
|
|
Packit |
42cdad |
number to its nearest integer and return that integer.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Coercions can be implemented either as a simple subroutine reference or as an
|
|
Packit |
42cdad |
inline generator subroutine. Using an inline generator is faster but more
|
|
Packit |
42cdad |
complicated.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=for Pod::Coverage BUILD clone_with_new_to
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 API
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This class provides the following methods.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 Specio::Coercion->new( ... )
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This method creates a new coercion object. It accepts the following named
|
|
Packit |
42cdad |
parameters:
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=over 4
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=item * from => $type
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
The type this coercion is from. The type must be an object which does the
|
|
Packit |
42cdad |
L<Specio::Constraint::Role::Interface> interface.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter is required.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=item * to => $type
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
The type this coercion is to. The type must be an object which does the
|
|
Packit |
42cdad |
L<Specio::Constraint::Role::Interface> interface.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter is required.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=item * coercion => sub { ... }
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
A subroutine reference implementing the coercion. It will be called as a
|
|
Packit |
42cdad |
method on the object and passed a single argument, the value to coerce.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
It should return the new value.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter is mutually exclusive with C<inline_generator>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Either this parameter or the C<inline_generator> parameter is required.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
You can also pass this option with the key C<using> in the parameter list.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=item * inline_generator => sub { ... }
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This should be a subroutine reference which returns a string containing a
|
|
Packit |
42cdad |
single term. This code should I<not> end in a semicolon. This code should
|
|
Packit |
42cdad |
implement the coercion.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
The generator will be called as a method on the coercion with a single
|
|
Packit |
42cdad |
argument. That argument is the name of the variable being coerced, something
|
|
Packit |
42cdad |
like C<'$_[0]'> or C<'$var'>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter is mutually exclusive with C<coercion>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Either this parameter or the C<coercion> parameter is required.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
You can also pass this option with the key C<inline> in the parameter list.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=item * inline_environment => {}
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This should be a hash reference of variable names (with sigils) and values for
|
|
Packit |
42cdad |
that variable. The values should be I<references> to the values of the
|
|
Packit |
42cdad |
variables.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This environment will be used when compiling the coercion as part of a
|
|
Packit |
42cdad |
subroutine. The named variables will be captured as closures in the generated
|
|
Packit |
42cdad |
subroutine, using L<Eval::Closure>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
It should be very rare to need to set this in the constructor. It's more
|
|
Packit |
42cdad |
likely that a special coercion subclass would need to provide values that it
|
|
Packit |
42cdad |
generates internally.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter defaults to an empty hash reference.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=item * declared_at => $declared_at
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter must be a L<Specio::DeclaredAt> object.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This parameter is required.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=back
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->from(), $coercion->to(), $coercion->declared_at()
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
These methods are all read-only attribute accessors for the corresponding
|
|
Packit |
42cdad |
attribute.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->description
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This returns a string describing the coercion. This includes the names of the
|
|
Packit |
42cdad |
to and from type and where the coercion was declared, so you end up with
|
|
Packit |
42cdad |
something like C<'coercion from Foo to Bar declared in package My::Lib
|
|
Packit |
42cdad |
(lib/My/Lib.pm) at line 42'>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->coerce($value)
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Given a value of the right "from" type, returns a new value of the "to" type.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This method does not actually check that the types of given or return values.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->inline_coercion($var)
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Given a variable name like C<'$_[0]'> this returns a string with code for the
|
|
Packit |
42cdad |
coercion.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Note that this method will die if the coercion does not have an inline
|
|
Packit |
42cdad |
generator.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->can_be_inlined()
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This returns true if the coercion has an inline generator I<and> the
|
|
Packit |
42cdad |
constraint it is from can be inlined. This exists primarily for the benefit of
|
|
Packit |
42cdad |
the C<inline_coercion_and_check()> method for type constraint object.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->inline_environment()
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This returns a hash defining the variables that need to be closed over when
|
|
Packit |
42cdad |
inlining the coercion. The keys are full variable names like C<'$foo'> or
|
|
Packit |
42cdad |
C<'@bar'>. The values are I<references> to a variable of the matching type.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->clone()
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Returns a clone of this object.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head2 $coercion->clone_with_new_to($new_to_type)
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This returns a clone of the coercion, replacing the "to" type with a new
|
|
Packit |
42cdad |
one. This is intended for use when the to type itself is being cloned as part
|
|
Packit |
42cdad |
of importing that type. We need to make sure the newly cloned coercion has the
|
|
Packit |
42cdad |
newly cloned type as well.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 ROLES
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This class does the L<Specio::Role::Inlinable> role.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 SUPPORT
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Bugs may be submitted at L<https://github.com/houseabsolute/Specio/issues>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 SOURCE
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
The source code repository for Specio can be found at L<https://github.com/houseabsolute/Specio>.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 AUTHOR
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
Dave Rolsky <autarch@urth.org>
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=head1 COPYRIGHT AND LICENSE
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This software is Copyright (c) 2012 - 2017 by Dave Rolsky.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
This is free software, licensed under:
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
The Artistic License 2.0 (GPL Compatible)
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
The full text of the license can be found in the
|
|
Packit |
42cdad |
F<LICENSE> file included with this distribution.
|
|
Packit |
42cdad |
|
|
Packit |
42cdad |
=cut
|