tl;dr: A major version bump should be a semantic versioning change to the default feature bundle.
I’ve heard calls for a Perl 7 along the lines of 5.32 but with better defaults. This document describes how I imagine that might possibly work.
I have not paid any attention to actual discussions/flamewars going on about Perl 7. I have no idea about the technical feasibility of what I describe. And I no longer have time for, nor an economic stake in, contributing to make it happen.
I offer this in the spirit of a knowledgeable community member imagining the future I’d like to live in.
Perl 5 already has a mechanism for opting into different semantics via the
feature
pragma. Features are
granular bits of syntax or semantics like say
or unicode_strings
.
These are organized into ‘feature bundles’, like :5.12
, which are enabled
via the feature
pragma use feature ':5.12'
, or via a shortcut like use v5.12
.
[For the moment, I’m going to ignore the peculiarity that use feature ':5.12'
doesn’t set strict
but use v5.12
does.1]
This has the very nice property that any Perl code that declares a requirement on a version of Perl gets the semantics released with that version, even when run on a newer Perl.
Because the feature bundles effectively follow semantic versioning, each minor version might add features to the default bundle for that version. For example:
:5.14 say state switch unicode_strings
:5.16 say state switch unicode_strings
unicode_eval evalbytes current_sub fc
If our code was written with use v5.14
– which works on any perl from
v5.14 or later – and we decided that we really needed to add fc
, we can
bump our declaration to use v5.16
. This is safe, because we know that
minor versions only add features and never remove them.
But let’s say that we decide that the switch
feature is a problem, and we
want to remove it from the default bundle in the next version of Perl,
whatever that might be. Following the rules of semantic versioning, we
never remove functionality in a minor version, so we need a major version
bump. In this case, 6 is out, so we need 7.
To illustrate this, let’s ignore all the versions of Perl after v5.16 and
their features, and imagine a counterfactual history where v7 followed
v5.16 with a feature bundle that removes switch
and also adds a new,
fictional klingon
feature:
:7.0 say state unicode_strings
unicode_eval evalbytes current_sub fc klingon
Seeing a new major version, we’d be careful bumping from use v5.16
to
use v7.0
to get klingon
because we know there might be a breaking
change. If we want to get ‘switch’ back, we can add it back with the feature
pragma because it’s still there, just not part of the default bundle:
use v7.0;
use feature 'switch';
This seems like a completely natural reason for major version bumps and a very familiar, explainable mechanism for it.
A major version bump is just a semantic
versioning change to the default feature bundle. Code with older
declarations gets the older semantics. Code that needs a mix of old and
new semantics can restore them with feature
.
But how do we remove things that aren’t features? The answer is
straightforward: retroactively declare them to be features. This has
already happened for indirect
. If you look at the feature
docs in
Perl 5.32, you’ll see that indirect
has been added to the :default
feature bundle and every bundle after it. This creates the opportunity to
remove indirect
in a :7.0
bundle.
This works in a wonderfully subtle way. Consider indirect
. On Perl
5.30, having no declaration or a use v5.30
declaration gave you
indirect
because it was implicit. On Perl 5.32, the same is true. If
you try to turn off indirect
with the feature
pragma you need perl 5.32
anyway, so the only sensible use of the feature is this:
use v5.32;
no feature 'indirect';
I think retroactive feature definition is how all removals should work. And if for some technical reason, some piece of Perl’s syntax or semantics that people want to remove can’t be expressed as a feature, then it shouldn’t be removed.
The essential question that I think is at the crux of the controversy over
plans for Perl 7 is this: what features are enabled when no use vX
pragma
is in force? One view is that it should default to the feature bundle of the
current version of Perl – i.e. no minimum version declaration means “latest
features”, including default strictures.
I think this is misguided.
For one, this is the critical change that breaks legacy code. If no
version declaration is instead taken as equivalent to use v5
, then legacy
code continues to work.
Also, I believe code benefits from version declarations. Code with a version declaration is protected from being run on older perls. If code is assuming v7 semantics, shouldn’t it fail to run on v5.X? And the version declaration provides forward compatibility. It protects code from future versions of Perl that change semantics but provide backwards compatibility. If code is assuming v7 semantics, shouldn’t it continue to run on v8 without modification?
The ‘cost’ of requiring users to write use v7;
is small. It’s not hard
to teach or learn. It leads to better, safer code. And it is kinder to
existing users.
The next conundrum I have is about strict
. Currently, we
have the somewhat weird situation where use v5.12
or later enables
strict
, but use feature ':5.12'
does not.1
Should use feature ':7.0'
enable strict
? It would be more consistent
with the rest of the feature system: adding a default_strict
feature
would make the change visible to people comparing feature bundles across
versions. But it’s also a break with the past. On balance, I think it
would be a benefit. Default warnings could be handled the same way.
On the other hand, I’ve heard through the grapevine that strict by default
is complicated (though I’ve heard it in the context of default strict
without a version declaration). If leaving the current system alone is
less complex to implement, I think that’s fine, too. Most people will be
using use v7
, not use feature ':7.0'
and will get the benefit
regardless.
This leads me to consider the one-liner flags -e
and -E
. If
default_strict
is not a Perl 7 feature and if no version declaration is
equivalent to use v5
, then it all works as you expect already in Perl 5.
The -e
flag is equivalent to code without a version declaration. The -E
flag is equivalent to use feature ':<current>'
(which doesn’t turn on
strict).
If default warnings work like default strict (on with use v7
and not with
the :7.0
feature bundle), then it still works consistently as one expects.
But even if default_strict
and default_warnings
are features enabled
with the :7.0
bundle, the definitions of -e
and -E
are still
straightforward:
-e use v5
-E use v7; no strict; no warnings;
Why continue to have -E
avoid strict and warnings? I think that’s how
most people use one-liners and so minimizes annoyance and breakage. And it
minimizes conflict with the -w/W/x
flags.
The last thing I want to consider is when old features should be removed.
If Perl 7 removes indirect
from the default bundle, but keeps it
available for backwards compatibility (presumably marked ‘deprecated’), how
long should it stay available? If Perl 8 removes it, then code that
declares use v5
should error because Perl 8 can’t satisfy the feature
bundle.
There is cost to maintaining old code and there is cost to breaking user code. I suspect the right answer is that most features should stay around for backwards compatibility for a long time. But this is no different than the deprecation policy we have today and I see no reason for that to change with Perl 7 and beyond.
So what would I like in the Perl 7 feature bundle?
- Add default strict and warnings – either as part of the feature bundle
or enabled only with
use v7
. - Add
signatures
as a non-experimental feature. I think this requires adding a retroactiveprototypes
feature to bundles before v7. - Remove
indirect
. - Remove
switch
(because it requires an experimental feature to work).
I think that’s enough. It’s a step into a future where Perl major versions are defined by the breaking feature bundle changes they make.
In my proposed case, that’s removing three features (indirect
, switch
,
and a newly-defined prototypes
), and adding two code-breaking features
default_strict
and default_warnings
(either as literal features or via
version declaration).
Breaking changes should be rare. But they should be possible. The approach I’ve described makes it possible and minimizes the impact on legacy code.
I don’t know if what I’ve described is technically feasible, but as a user of Perl 5 and hopefully Perl 7, I hope it is.
-
Technically,
use v5.12
and later turn on only the parts of strict that haven’t been explicitly disabled previously. E.g.no strict 'refs'; use v5.12;
turns on strict except for strict refs. This complicates the design of Perl 7 default strict a bit, but I think that’s a small detail that can be worked out reasonably any number of ways without jeopardizing the vision I’ve described. ↩︎ ↩︎