The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package My::Constants;
use strict;
use warnings;
use Exporter 'import';
use constant {
    X => -2,
    Y => -1,
};
our @EXPORT_OK = qw(X Y);
use Constant::Export::Lazy (
    constants => {
        # This is the simplest way to go, just define plain constant
        # values.
        A => sub { 1 },
        B => sub { 2 },
        # You get a $ctx object that you can ->call() to retrieve the
        # values of other constants. This is how you can make some
        # constants depend on others without worrying about
        # ordering. Constants are still guaranteed to only be
        # fleshened once!
        SUM => sub {
            my ($ctx) = @_;
            $ctx->call('A') + $ctx->call('B'),
        },
        # For convenience you can also access other constants,
        # e.g. those defined with constant.pm
        SUM_INTEROP => sub {
            my ($ctx) = @_;
            $ctx->call('X') + $ctx->call('Y'),
        },
        # We won't call this and die unless someone requests it when
        # they import us.
        DIE => sub { die },
        # These subroutines are always called in scalar context, and
        # thus We'll return [3..4] here.
        #
        # Unlike the constant.pm that ships with perl itself we don't
        # support returning lists. So if you want to return lists you
        # have to return a reference to one.
        LIST => sub { wantarray ? (1..2) : [3..4] },
        # We can also supply a HashRef with "call" with the sub, and
        # "options" with options that clobber the global
        # options. Actually when you supply just a plain sub instead
        # of a HashRef we internally munge it to look like this more
        # verbose (and more flexible) structure.
        PI => {
            call    => sub { 3.14 },
            options => {
                override => sub {
                    my ($ctx, $name) = @_;
                    # You can simply "return;" here to say "I don't
                    # want to override", and "return undef;" if you
                    # want the constant to be undef.
                    return $ENV{PI} ? "Pi is = $ENV{PI}" : $ctx->call($name);
                },
                # This is an optional ref that'll be accessible via
                # $ctx->stash in any subs relevant to this constant
                # (call, override, after, ...)
                stash => {
                    # This `typecheck_rx` is in no way supported by
                    # Constant::Export::Lazy, it's just something
                    # we're passing around to the 'after' sub below.
                    typecheck_rx => qr/\d+\.\d+/s, # such an epicly buggy typecheck...
                },
            },
        },
    },
    options => {
        # We're still exporting some legacy constants via Exporter.pm
        wrap_existing_import => 1,
        # A general override so you can override other constants in
        # %ENV
        override => sub {
            my ($ctx, $name) = @_;
            return unless exists $ENV{$name};
            return $ENV{$name};
        },
        after => sub {
            my ($ctx, $name, $value, $source) = @_;

            if (defined(my $stash = $ctx->stash)) {
                my $typecheck_rx = $stash->{typecheck_rx};
                die "PANIC: The value <$value> for <$name> doesn't pass <$typecheck_rx>"
                    unless $value =~ $typecheck_rx;
            }

            print STDERR "Defined the constant <$name> with value <$value> from <$source>\n" if $ENV{DEBUG};
            return;
        },
    },
);

1;