view release on metacpan or search on metacpan
Revision history for Modern::Perl::Prelude
0.009 2026-03-23
- Explicitly document the Perl license in module metadata and docs
- Lower minimum supported Perl version from 5.30 to 5.26
- Update module, tests, README and build metadata for Perl 5.26+
- Extend CI matrix with Perl 5.26 and 5.28
- Preserve full test and documentation coverage for the public API
0.008 2026-03-22
- Add optional -always_true / always_true support via the true module
- Allow modules to omit a trailing 1; on Perl 5.30+ when always_true is enabled
- Support both flag-style and hash-style always_true imports
- Support explicit no Modern::Perl::Prelude -always_true / { always_true => 1 }
- Add true as a dependency and use true::VERSION in Makefile metadata
- Document always_true in POD and README
- Add coverage tests for always_true import and unimport behavior
- Preserve 0.007 work on hash-style imports and Test2::Tools::Spec conversion
0.007 2026-03-22
- Add documented hash-style import arguments via a single hash reference
- Support hash-style keys: utf8, class, defer, corinna
- Make -class and -corinna mutually exclusive for both flag-style and hash-style imports
- Add tests for hash-style utf8 import and hash-style corinna import
- Fix UTF-8 option tests to match real lexical behavior
- Silence once-only package variable warning in args test
- Finalize test suite for use/no and option validation paths
0.003 2026-03-17
- Make unimport honest: only undo native pragmata/features
- Fix no.t to test only reliably reversible native features
- Clarify POD about import-only compat layers
0.002 2026-03-17
- Add unimport support: no Modern::Perl::Prelude
- Add author tests
- Add GitHub Actions CI matrix for Perl 5.30 .. 5.42
- Add cpanfile
- Refresh distribution skeleton
0.001 2026-03-17
- First version
Makefile.PL view on Meta::CPAN
use v5.26;
use strict;
use warnings;
use ExtUtils::MakeMaker;
WriteMakefile(
NAME => 'Modern::Perl::Prelude',
VERSION_FROM => 'lib/Modern/Perl/Prelude.pm',
ABSTRACT_FROM => 'lib/Modern/Perl/Prelude.pm',
AUTHOR => 'Sergey Kovalev <skov@cpan.org>, Kirill Dmitriev <zaika.k1007@gmail.com>',
LICENSE => 'perl',
MIN_PERL_VERSION => '5.26.0',
CONFIGURE_REQUIRES => {
'ExtUtils::MakeMaker' => 0,
},
# Modern::Perl::Prelude
[](https://dev.perl.org/licenses/)
[](https://www.perl.org/)
[](https://github.com/neo1ite/Modern-Perl-Prelude/actions/workflows/ci.yml)
A small lexical prelude for writing modern-style Perl on Perl 5.26+.
## What it enables by default
- `strict`
- `-utf8`
- `-class`
- `-defer`
- `-corinna`
- `-always_true`
Examples:
```perl
use Modern::Perl::Prelude '-utf8';
use Modern::Perl::Prelude qw(
-class
-defer
);
use Modern::Perl::Prelude qw(
-class
-utf8
-always_true
);
```
### Hash-style
Hash-style arguments are supported as a **single hash reference**:
```perl
use Modern::Perl::Prelude {
utf8 => 1,
defer => 1,
always_true => 1,
};
```
Supported hash keys:
- `utf8`
- `class`
For compatibility-layer options (`class`, `defer`, `corinna`), a true scalar enables the option. A hash reference also enables it and is passed through to the underlying module's `import`.
For `always_true`, use a boolean value.
### `-utf8` / `utf8`
Enables source-level UTF-8, like:
```perl
use Modern::Perl::Prelude '-utf8';
```
or:
```perl
use Modern::Perl::Prelude {
utf8 => 1,
};
```
### `-class` / `class`
Enables `Feature::Compat::Class` on demand:
```perl
use Modern::Perl::Prelude '-class';
class Point {
field $x :param = 0;
field $y :param = 0;
method sum {
return $x + $y;
}
}
```
### `-defer` / `defer`
Enables `Feature::Compat::Defer` on demand:
```perl
use Modern::Perl::Prelude '-defer';
{
defer { warn "leaving scope\n" };
...
}
```
### `-corinna` / `corinna`
Enables direct `Object::Pad` / Corinna-like syntax on demand:
```perl
use Modern::Perl::Prelude '-corinna';
class Person {
field $name :param;
field $age :param = 0;
method greet {
return "Hello, I'm $name and I'm $age years old";
}
}
```
Hash-style example:
```perl
use Modern::Perl::Prelude {
corinna => {},
utf8 => 1,
};
```
### `-always_true` / `always_true`
Enables automatic true return for the currently-compiling file, so a module can omit the trailing:
```perl
1;
```
Example module without `1;`:
```perl
use Modern::Perl::Prelude qw(
-class
-utf8
-always_true
);
class My::App::Person {
field $name :param;
field $age :param = 0;
method greet {
return "Hello, I'm $name and I'm $age years old";
}
}
```
Hash-style example:
```perl
use Modern::Perl::Prelude {
class => 1,
utf8 => 1,
always_true => 1,
};
class My::App::Person {
field $name :param;
}
```
## Option compatibility rules
Any non-conflicting combination is allowed.
`-class` and `-corinna` are intentionally mutually exclusive.
The same rule applies to hash-style arguments:
```perl
use Modern::Perl::Prelude {
class => 1,
corinna => 1,
}; # dies
```
## Usage
Basic usage:
```perl
use Modern::Perl::Prelude;
state $counter = 0;
my $s = trim(" hello ");
my $folded = fc("StraÃe");
try {
die "boom\n";
}
catch ($e) {
warn $e;
}
```
With `Feature::Compat::Class`:
```perl
use Modern::Perl::Prelude qw(
-class
-defer
);
class Example {
field $value :param = 0;
method value {
return $value;
}
{
defer { warn "scope ended\n" };
my $obj = Example->new(value => 42);
say $obj->value;
}
```
With `Object::Pad`:
```perl
use Modern::Perl::Prelude {
corinna => {},
utf8 => 1,
};
class Person {
field $name :param;
field $age :param = 0;
method greet {
return "Hello, I'm $name and I'm $age years old";
}
}
my $p = Person->new(name => 'José');
say $p->greet;
```
With `always_true` for a module file:
```perl
use Modern::Perl::Prelude qw(
-class
-utf8
-always_true
);
class My::App::Person {
field $name :param;
}
# no trailing 1;
```
## Lexical disabling
You can disable native pragmata/features lexically again:
```perl
no Modern::Perl::Prelude;
```
This reliably disables native pragmata/features managed directly by the module, such as:
- `strict`
- `warnings`
- `say`
- `state`
- `fc`
- `utf8`
Compatibility layers are treated as import-only for cross-version use on Perl 5.30+, so they are not guaranteed to be symmetrically undone by:
```perl
no Modern::Perl::Prelude;
```
This applies to:
- `Feature::Compat::Try`
- `Feature::Compat::Class`
- `Feature::Compat::Defer`
- `Object::Pad`
- `builtin::compat`
`always_true` is different: it is file-scoped, and you can explicitly disable it for the current file with:
```perl
no Modern::Perl::Prelude '-always_true';
```
or:
```perl
no Modern::Perl::Prelude { always_true => 1 };
```
## Design goals
This module is intended as a small project prelude for codebases that want a more modern Perl style while keeping runtime compatibility with Perl 5.30+.
It is implemented as a lexical wrapper using `Import::Into`, so pragmata and lexical features affect the caller's scope rather than the wrapper module itself.
Optional compatibility layers are loaded lazily and only when explicitly requested.
lib/Modern/Perl/Prelude.pm view on Meta::CPAN
package Modern::Perl::Prelude;
use v5.26;
use strict;
use warnings;
# ABSTRACT: Project prelude for modern Perl style on Perl 5.26+
our $VERSION = '0.009';
use Import::Into ();
use strict ();
lib/Modern/Perl/Prelude.pm view on Meta::CPAN
}
1;
__END__
=pod
=head1 NAME
Modern::Perl::Prelude - Project prelude for modern Perl style on Perl 5.26+
=head1 SYNOPSIS
use Modern::Perl::Prelude;
state $counter = 0;
my $s = trim(" hello ");
try {
die "boom\n";
}
catch ($e) {
warn $e;
}
Flag-style optional imports:
use Modern::Perl::Prelude '-utf8';
use Modern::Perl::Prelude qw/-class -defer/;
use Modern::Perl::Prelude qw(-corinna -always_true);
Hash-style optional imports:
use Modern::Perl::Prelude {
utf8 => 1,
defer => 1,
always_true => 1,
};
Disable native pragmata/features lexically again:
no Modern::Perl::Prelude;
no Modern::Perl::Prelude '-utf8';
no Modern::Perl::Prelude { utf8 => 1 };
no Modern::Perl::Prelude '-always_true';
=head1 DESCRIPTION
This module bundles a small, opinionated set of pragmata, features, and
compatibility layers for writing Perl in a Perl 5.40+-style while staying
runnable on Perl 5.26+.
It enables:
=over 4
lib/Modern/Perl/Prelude.pm view on Meta::CPAN
Supported flags:
-utf8
-class
-defer
-corinna
-always_true
Examples:
use Modern::Perl::Prelude '-utf8';
use Modern::Perl::Prelude qw(
-class
-defer
);
use Modern::Perl::Prelude qw(
-class
-utf8
-always_true
);
=head2 Hash-style
Hash-style arguments must be passed as a single hash reference:
use Modern::Perl::Prelude {
utf8 => 1,
defer => 1,
always_true => 1,
};
Supported hash keys:
=over 4
=item * C<utf8>
lib/Modern/Perl/Prelude.pm view on Meta::CPAN
=item * C<-defer> / C<defer> enables C<defer>
=item * C<-corinna> / C<corinna> enables class syntax via C<Object::Pad>
=item * C<-always_true> / C<always_true> enables automatic true return for the current file
=back
=head1 UNIMPORT
C<no Modern::Perl::Prelude> reliably disables native pragmata/features
managed by this module:
strict
warnings
say
state
fc
utf8
Compatibility layers such as C<Feature::Compat::Try>,
C<Feature::Compat::Class>, C<Feature::Compat::Defer>, C<Object::Pad>, and
C<builtin::compat> are treated as import-only for cross-version use on
Perl 5.26+ and are not guaranteed to be symmetrically undone by
C<no Modern::Perl::Prelude>.
C<always_true> is an exception: C<no Modern::Perl::Prelude '-always_true'> or
no Modern::Perl::Prelude { always_true => 1 };
disables the automatic true-return behavior for the current file.
=head1 DESIGN NOTES
This is a lexical prelude module. It is implemented via C<Import::Into> so
that pragmata and lexical functions affect the caller's scope, not the scope
of this wrapper module itself.
Optional compatibility layers are loaded lazily, only when explicitly
t/00-load.t view on Meta::CPAN
use v5.26;
use strict;
use warnings;
use Test::More;
BEGIN {
use_ok('Modern::Perl::Prelude');
}
done_testing;
t/01-import.t view on Meta::CPAN
use v5.26;
use strict;
use warnings;
use utf8;
use Test::More;
my $ok = eval <<'PERL';
package Local::Prelude::Smoke;
use Modern::Perl::Prelude;
our %RESULT;
sub run {
state $counter = 0;
$counter++;
my $out = '';
{
open my $fh, '>', \$out or die "open scalar fh failed: $!";
use v5.26;
use strict;
use warnings;
use Test::More;
my $ok_say = eval <<'PERL';
package Local::Prelude::No::Say;
use Modern::Perl::Prelude;
no Modern::Perl::Prelude;
say 'hello';
1;
PERL
ok(!$ok_say, 'say unavailable after no Modern::Perl::Prelude');
my $ok_fc = eval <<'PERL';
package Local::Prelude::No::Fc;
use Modern::Perl::Prelude;
no Modern::Perl::Prelude;
my $x = fc("StraÃe");
1;
PERL
ok(!$ok_fc, 'fc unavailable after no Modern::Perl::Prelude');
my $ok_state = eval <<'PERL';
package Local::Prelude::No::State;
use Modern::Perl::Prelude;
no Modern::Perl::Prelude;
{
state $v = 1;
}
1;
PERL
ok(!$ok_state, 'state unavailable after no Modern::Perl::Prelude');
done_testing;
t/03-args.t view on Meta::CPAN
use v5.26;
use strict;
use warnings;
use utf8;
use Test::More;
my $ok_utf8 = eval <<'PERL';
package Local::Prelude::Args::Utf8;
use Modern::Perl::Prelude '-utf8';
our $OK;
my $Ñж = "Ñж";
$OK = ($Ñж eq "Ñж") ? 1 : 0;
1;
PERL
ok($ok_utf8, 'use Modern::Perl::Prelude -utf8 compiles')
or diag $@;
{
no warnings 'once';
ok($Local::Prelude::Args::Utf8::OK, '-utf8 enables utf8 source semantics');
}
my $ok_no_utf8 = eval <<'PERL';
package Local::Prelude::Args::NoUtf8;
use Modern::Perl::Prelude '-utf8';
no Modern::Perl::Prelude '-utf8';
1;
PERL
ok($ok_no_utf8, 'no Modern::Perl::Prelude -utf8 accepts known option')
or diag $@;
my $ok_hash_utf8 = eval <<'PERL';
package Local::Prelude::Args::HashUtf8;
use Modern::Perl::Prelude { utf8 => 1 };
our $OK;
my $Ñж = "Ñж";
$OK = ($Ñж eq "Ñж") ? 1 : 0;
1;
PERL
ok($ok_hash_utf8, 'hash-style utf8 option compiles')
or diag $@;
{
no warnings 'once';
ok($Local::Prelude::Args::HashUtf8::OK, 'hash-style utf8 enables utf8 source semantics');
}
my $ok_hash_no_utf8 = eval <<'PERL';
package Local::Prelude::Args::HashNoUtf8;
use Modern::Perl::Prelude { utf8 => 1 };
no Modern::Perl::Prelude { utf8 => 1 };
1;
PERL
ok($ok_hash_no_utf8, 'hash-style no accepts known option')
or diag $@;
my $ok_always_true_flag = eval <<'PERL';
package Local::Prelude::Args::AlwaysTrueFlag;
use Modern::Perl::Prelude '-always_true';
1;
PERL
ok($ok_always_true_flag, 'flag-style always_true option compiles')
or diag $@;
my $ok_always_true_hash = eval <<'PERL';
package Local::Prelude::Args::AlwaysTrueHash;
use Modern::Perl::Prelude { always_true => 1 };
1;
PERL
ok($ok_always_true_hash, 'hash-style always_true option compiles')
or diag $@;
my $ok_bad_use = eval <<'PERL';
package Local::Prelude::Args::BadUse;
use Modern::Perl::Prelude '-bogus';
1;
PERL
ok(!$ok_bad_use, 'unknown option in use dies');
like(
$@,
qr/^Modern::Perl::Prelude: unknown import option "-bogus"/,
'unknown option in use gives expected error',
);
my $ok_bad_no = eval <<'PERL';
package Local::Prelude::Args::BadNo;
use Modern::Perl::Prelude;
no Modern::Perl::Prelude '-bogus';
1;
PERL
ok(!$ok_bad_no, 'unknown option in no dies');
like(
$@,
qr/^Modern::Perl::Prelude: unknown import option "-bogus"/,
'unknown option in no gives expected error',
);
my $ok_bad_hash_key = eval <<'PERL';
package Local::Prelude::Args::BadHashKey;
use Modern::Perl::Prelude { bogus => 1 };
1;
PERL
ok(!$ok_bad_hash_key, 'unknown hash-style key dies');
like(
$@,
qr/^Modern::Perl::Prelude: unknown import key "bogus"/,
'unknown hash-style key gives expected error',
);
my $ok_mixed = eval <<'PERL';
package Local::Prelude::Args::Mixed;
use Modern::Perl::Prelude(
'-utf8',
{},
);
1;
PERL
ok(!$ok_mixed, 'mixed flag-style and hash-style args die');
like(
$@,
qr/^Modern::Perl::Prelude: hash-style arguments must be passed as a single hash reference/,
'mixed args give expected error',
);
my $ok_conflict_flags = eval <<'PERL';
package Local::Prelude::Args::ConflictFlags;
use Modern::Perl::Prelude qw(
-class
-corinna
);
1;
PERL
ok(!$ok_conflict_flags, 'conflicting -class and -corinna flags die');
like(
$@,
qr/^Modern::Perl::Prelude: options "-class" and "-corinna" are mutually exclusive/,
'flag conflict gives expected error',
);
my $ok_conflict_hash = eval <<'PERL';
package Local::Prelude::Args::ConflictHash;
use Modern::Perl::Prelude {
class => 1,
corinna => 1,
};
1;
PERL
ok(!$ok_conflict_hash, 'conflicting class/corinna hash-style args die');
like(
$@,
qr/^Modern::Perl::Prelude: options "-class" and "-corinna" are mutually exclusive/,
'hash-style conflict gives expected error',
);
done_testing;
t/04-class-defer.t view on Meta::CPAN
use Test2::Tools::Spec;
sub _run_eval {
my ($code) = @_;
local $@;
my $ret = eval $code;
my $err = $@;
return ($ret, $err);
}
describe 'Modern::Perl::Prelude optional -class/-defer imports' => sub {
it 'compiles and runs class and defer via flag-style imports' => sub {
my ($ret, $err) = _run_eval(<<'PERL');
package Local::Prelude::Compat::Smoke;
use Modern::Perl::Prelude qw(
-class
-defer
);
class Local::Prelude::Compat::Point {
field $x :param = 0;
field $y :param = 0;
method sum {
return $x + $y;
t/04-class-defer.t view on Meta::CPAN
];
PERL
ok($ret, 'optional -class and -defer imports compile and run')
or diag $err;
is($ret->[0], 5, 'class syntax works via -class');
is($ret->[1], 'enter,leave,defer', 'defer syntax works via -defer');
};
it 'accepts no Modern::Perl::Prelude with -class and -defer options' => sub {
my ($ok, $err) = _run_eval(<<'PERL');
package Local::Prelude::Compat::No;
use Modern::Perl::Prelude qw(
-class
-defer
);
no Modern::Perl::Prelude qw(
-class
-defer
);
1;
PERL
ok($ok, 'no Modern::Perl::Prelude accepts -class and -defer options')
or diag $err;
};
};
done_testing;
t/05-corinna.t view on Meta::CPAN
use Test2::Tools::Spec;
sub _run_eval {
my ($code) = @_;
local $@;
my $ret = eval $code;
my $err = $@;
return ($ret, $err);
}
describe 'Modern::Perl::Prelude optional corinna import' => sub {
it 'compiles and runs hash-style corinna import with utf8' => sub {
my ($ret, $err) = _run_eval(<<'PERL');
package Local::Prelude::Corinna::Smoke;
use Modern::Perl::Prelude {
corinna => {},
utf8 => 1,
};
class Local::Prelude::Corinna::Person {
field $name :param;
field $age :param = 0;
method greet {
return "Hello, I'm $name and I'm $age years old";
t/05-corinna.t view on Meta::CPAN
is($ret->[1], 31, 'Object::Pad field mutation works');
is($ret->[2], "Hello, I'm Bob and I'm 0 years old", 'default field value works');
is($ret->[3], 'Developer', 'subclass field reader method works');
is($ret->[4], 50_000, 'initial subclass state works');
is($ret->[5], 55_000, 'subclass method with argument works');
is($ret->[6], 29, 'inherited method works');
is($ret->[7], "Hello, I'm Charlie and I'm 29 years old", 'inherited greet works');
ok($ret->[8], 'hash-style corinna works together with utf8');
};
it 'accepts no Modern::Perl::Prelude with hash-style corinna option' => sub {
my ($ok, $err) = _run_eval(<<'PERL');
package Local::Prelude::Corinna::No;
use Modern::Perl::Prelude { corinna => {} };
no Modern::Perl::Prelude { corinna => {} };
1;
PERL
ok($ok, 'no Modern::Perl::Prelude accepts hash-style corinna option')
or diag $err;
};
};
done_testing;
t/06-always-true.t view on Meta::CPAN
return ($ok, $err);
}
my $tmp = tempdir(CLEANUP => 1);
local @INC = ($tmp, @INC);
_write_module(
$tmp,
'Local::AlwaysTrue::Flag',
<<'PERL',
use Modern::Perl::Prelude qw(
-class
-utf8
-always_true
);
class Local::AlwaysTrue::Flag {
field $name :param;
method greet {
return "Hello, $name";
t/06-always-true.t view on Meta::CPAN
Local::AlwaysTrue::Flag->new(name => 'José')->greet,
'Hello, José',
'flag-style always_true works for a class module',
);
}
_write_module(
$tmp,
'Local::AlwaysTrue::Hash',
<<'PERL',
use Modern::Perl::Prelude {
class => 1,
utf8 => 1,
always_true => 1,
};
class Local::AlwaysTrue::Hash {
field $name :param;
method greet {
return "Hi, $name";
t/06-always-true.t view on Meta::CPAN
Local::AlwaysTrue::Hash->new(name => 'José')->greet,
'Hi, José',
'hash-style always_true works for a class module',
);
}
_write_module(
$tmp,
'Local::AlwaysTrue::Disabled',
<<'PERL',
use Modern::Perl::Prelude {
class => 1,
always_true => 1,
};
no Modern::Perl::Prelude { always_true => 1 };
class Local::AlwaysTrue::Disabled {
field $name :param;
}
0;
PERL
);
my ($ok_disabled, $err_disabled) = _require_module('Local::AlwaysTrue::Disabled');
ok(!$ok_disabled, 'no Modern::Perl::Prelude { always_true => 1 } restores normal require behavior');
like(
$err_disabled,
qr/did not return a true value/,
'disabled always_true makes the module fail without trailing 1',
);
done_testing;