Constant-Export-Lazy

 view release on metacpan or  search on metacpan

lib/Constant/Export/Lazy.pm  view on Meta::CPAN

                    for my $constant (keys %$constants) {
                        my @export_tags = @{$constants->{$constant}->{options}->{stash}->{export_tags} || []};
                        push @{$export_tags{$_}} => $constant for @export_tags;
                    }
                    \%export_tags;
                };

                my @gimme = map {
                    /^:/ ? @{$export_tags->{$_}} : $_
                } @$import_args;

                return \@gimme;
            },
        },
    );

    1;

And this is an example of using it in some user code (from
F<t/synopsis_tags.t> in the source distro):

    package My::More::User::Code;
    use strict;
    use warnings;
    use Test::More qw(no_plan);
    use lib 't/lib';
    use My::Constants::Tags qw(
        KG_TO_MG
        :math
        :alphabet
    );

    is(KG_TO_MG, 10**6);
    is(A, "A");
    is(B, "B");
    is(C, "C");
    like(PI, qr/^3\.14/);

And running it gives:

    $ perl -Ilib t/synopsis_tags.t
    ok 1
    ok 2
    ok 3
    ok 4
    ok 5
    1..5

=head1 DESCRIPTION

This is a library to write lazy exporters of constant
subroutines. It's not meant to be a user-facing constant exporting
API, it's something you use to write user-facing constant exporting
APIs.

There's dozens of modules on the CPAN that define constants in one way
or another, why did I need to write this one?

=head2 It's lazy

Our constants are fleshened via callbacks that are guaranteed to be
called only once for the lifetime of the process (not once per
importer or whatever), and we only call the callbacks lazily if
someone actually requests that a constant of ours be defined.

This makes it easy to have one constant exporting module that runs in
different environments, and generates some subset of its constants
depending on what the program that's using it actually needs.

Some data that you may want to turn into constants may require modules
that aren't available everywhere, queries to databases that aren't
available everywhere, or make certain assumptions about the
environment they're running under that may not be true across all your
environments.

By only defining those constants you actually need via callbacks
managing all these special-cases becomes a lot easier.

=head2 It makes it easier to manage creating constants that require other constants

Maybe you have one constant indicating whether you're running in a dev
environment, and a bunch of other constants that are defined
differently if the dev environment constant is true.

Now say you have several hundred constants like that, managing the
inter-dependencies and ensuring that they're all defined in the right
order with dependencies before dependents quickly gets messy.

All this complexity becomes a non-issue when you use this module. When
you define a constant you get a callback object that can give you the
value of other constants.

When you look up another constant we'll either generate it if it
hasn't been materialized yet, or look up the materialized value in the
symbol table if it has.

Thus we end up with a Makefile-like system where you can freely use
whatever other constants you like when defining your constants, and
we'll lazily define the entire tree of constants on-demand.

You only have to be careful not to introduce circular dependencies.

=head1 API

Our API is exposed via a nested key-value pair list passed to C<use>,
see the L</SYNOPSIS> for an example. Here's description of the data
structure you can pass in:

=head2 constants

This is a key-value pair list of constant names to either a subroutine
or a hash with L</call> and optional L<options|/options
(local)>. Internally we just convert the former type of call into the
latter, i.e. C<< CONST => sub {...} >> becomes C<< CONST => { call =>
sub { ... } } >>.

=head3 call

The subroutine we'll call with a L<context
object|Constant::Export::Lazy/"CONTEXT OBJECT"> to fleshen the
constant.

It's guaranteed that this sub will only ever be called once for the
lifetime of the process, except if you manually call it multiple times
during an L</override>.

=head3 options (local)

Our options hash to override the global L</options>. The semantics are
exactly the same as for the global hash.

=head2 options

We support various options, most of these can be defined either
globally if you want to use them for all the constants, or locally to
one constant at a time with the more verbose hash invocation to

lib/Constant/Export/Lazy.pm  view on Meta::CPAN

use.

=head3 stash

This is a reference that you can provide for your own use, we don't
care what's in it. It'll be accessible via the L<context
object|Constant::Export::Lazy/"CONTEXT OBJECT">'s C<stash> method
(i.e. C<< my $stash = $ctx->stash >>) for L</call>, L</override> and
L</after> calls relevant to its scope, i.e. global if you define it
globally, otherwise local if it's defined locally.

=head3 private_name_munger

This callback can be defined either globally or locally. When it's
provided it'll be used to munge the internal name of the subroutine we
define in the exporting package.

This allows for preventing the anti-pattern of user code not importing
constants before using them. To take the example in the
L<synopsis|/SYNOPSIS> it's for preventing C<My::Constants::PI> and
C<My::User::Code::PI> interchangeably, using this facility we can
change C<My::Constants::PI> to
e.g. C<My::Constants::SOME_OPAQUE_VALUE_PI>.

This is useful because users used to using other constant modules
might be in the habit of using non-imported and imported names
interchangeably.

This is fine when the constant exporting module isn't lazy, however
with Constant::Export::Lazy this relies on someone else having
previously defined the constant at a distance, and if that someone
goes away this'll silently turn into an error at a distance.

By using the C<private_name_munger> option you can avoid this
happening in the first place by specifying a subroutine like:

    private_name_munger => sub {
        my ($gimme) = @_;

        # We guarantee that these constants are always defined by us,
        # and we don't want to munge them because legacy code calls
        # them directly for historical reasons.
        return if $gimme =~ /^ALWAYS_DEFINED_/;

        state $now = time();
        return $gimme . '_TIME_' . $now;
    },

Anyone trying to call that directly from your exporting package as
opposed to importing into their package will very quickly discover
that it doesn't work.

Because this is called really early on this routine doesn't get passed
a C<$ctx> object, just the name of the constant you might want to
munge. To skip munging it return the empty list, otherwise return a
munged name to be used in the private symbol table.

We consider this a purely functional subroutine and you B<MUST> return
the same munged name for the same C<$gimme> because we might resolve
that C<$gimme> multiple times. Failure to do so will result your
callbacks being redundantly re-defined.

=head1 CONTEXT OBJECT

As discussed above we pass around a context object to all callbacks
that you can define. See C<$ctx> in the L</SYNOPSIS> for examples.

This objects has only two methods:

=over

=item * C<call>

This method will do all the work of fleshening constants via the sub
provided in the L</call> option, taking the L</override> callback into
account if provided, and if applicable calling the L</after> callback
after the constant is defined.

If you call a subroutine you haven't defined yet (or isn't being
imported directly) we'll fleshen it if needed, making sure to only
export it to a user's namespace if explicitly requested.

See L</override> for caveats with calling this inside the scope of an
override callback.

=item * C<stash>

An accessor for the L</stash> reference, will return the empty list if
there's no stash reference defined.

=back

=head1 AUTHOR

Ævar Arnfjörð Bjarmason <avar@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Ævar Arnfjörð Bjarmason <avar@cpan.org>

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut



( run in 1.960 second using v1.01-cache-2.11-cpan-140bd7fdf52 )