Blame lib/Specio/Coercion.pm

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