Front page | perl.perl5.porters |
Postings from January 2022
Re: RFC: Amores. Introducing a `module` keyword
Thread Previous
|
Thread Next
From:
Dan Book
Date:
January 26, 2022 01:50
Subject:
Re: RFC: Amores. Introducing a `module` keyword
Message ID:
CABMkAVXWheY4g4SCeGQpaeZnSeBrKxVQRCaAaQ+TP1dy89560A@mail.gmail.com
On Tue, Jan 25, 2022 at 11:40 AM Ovid via perl5-porters <
perl5-porters@perl.org> wrote:
> Author: Curtis "Ovid" Poe <curtis.poe@gmail.com>
> Sponsor: Paul "LeoNerd" Evans
> ID:
> Status: Proposal
> Title: "Amores", the procedural sister to "Corinna"
>
> Note: Paul asked that I submit this RFC, but he didn't specifically say he
> would be the sponsor. I'm just hoping :)
>
> *Abstract*
>
> Add a module keyword with basic, modern features to support common Perl
> use cases.
>
> Similar to Corinna ...
>
> class My::Class :version(42) {
> ...
> }
>
> ... we have Amores, the procedural version, using a similarly structured
> KIM syntax (keyword, identifier, modifier:
> https://ovid.github.io/articles/language-design-consistency.html):
>
> use feature 'module';
>
> module My::Module :version(42) {
>
> # these subs are examples only. Their behavior isn't
> # part of the proposal, though `:export` is.
> sub my_sub :export ($arg1, $args2) { ... }
> sub trim :export(strings) ($string) { ... }
> sub ltrim :export(strings) ($string) { ... }
> sub rtrim :export(strings) ($string) { ... }
> sub inc :export(numbers) ($num) { ... }
> sub dec :export(numbers) ($num) { ... }
> sub munge ($thing) { ... }
> }
>
> use My::Module qw(ltrim my_sub);
> use My::Module ':strings';
> use My::Module 'munge'; # fatal, No :export modifier
>
>
> *Motivation*
>
> To bring more consistency and better defaults to Perl, but in a safe,
> lexical scope that offers a strong hint to the Perl developer that this is
> not your grandfather's Perl.
>
> *Rationale*
>
> Sometimes we have a bunch of things we'd like as defaults in Perl, but
> it's not always clear that we can enable them. Even something as simple as
> writing use v5.36.0 can break legacy code unless we use a block to limit
> its scope (I've had quite a few clients in the past couple of years who've
> had Perl as fragile as Kanye West's ego).
>
> The idea behind Amores is to provide a convenient block-like structure
> with a syntax that mirrors Corinna, for a modern, consistent "feel" to the
> language. It's also designed to simplify exporting.
>
> *Syntax*
>
> We add only one keyword: module. Two modifiers (attributes) are added,
> :version and :export (:version also exists in Corinna and could be
> generally applicable outside the language).
>
> *Keyword: module*
>
> This is only enabled with use feature 'module';
>
> The structure of this is:
>
> module MODULE_NAME OPTIONAL_VERSION POSTFIXBLOCK
>
> At the end of the block, it yields a 1 whether you use or require it,
> eliminating the need for the trailing 1 (see also:
> https://github.com/Perl/perl5/issues/17921)
>
> Example:
>
> module My::Module {
> ...
> }
> # or with a version
> module My::Module :version(42) {
> ...
> }
>
> The latter example is equivalent to:
>
> package My::Module {
> our $VERSION = 42;
> use strict;
> use warnings;
> use feature 'signatures';
> no feature 'indirect';
> no feature 'multidimensional';
> no feature 'bareword_filehandles';
> ...
> }
>
> The feature list is deliberately kept small and sane to minimize danger. use
> utf8 was removed from the above list after P5P feedback. This discussion
> was referenced in that conversation:
> https://www.nntp.perl.org/group/perl.perl5.porters/2021/08/msg261164.html.
>
> In other words, lots of useful boilerplate goes away.
>
> Happy to discuss adding other features if necessary/useful.
>
> *Modifier: :version(...)*
>
> Optionally declared after the module name (and *only* after the module
> name to make it extremely predictable). Any valid version number may be
> specified.
>
> *Modifier: :export*
>
> This attribute controls exporting of functions from a module. It does not
> use the import mechanism, though if an import sub is found, it will
> still be called in the normal manner. It just won't be needed for importing.
>
> module My::Module :version(42) {
>
> # these subs are examples only. Their behavior isn't part of the
> # proposal, though `:export` is.
> sub my_sub :export ($arg1, $args2) { ... }
> sub trim :export(strings) ($string) { ... }
> sub ltrim :export(strings) ($string) { ... }
> sub rtrim :export(strings) ($string) { ... }
> sub inc :export(numbers) ($num) { ... }
> sub dec :export(numbers) ($num) { ... }
> sub munge ($thing) { ... }
> }
>
> This would mostly follow the syntax of the Perl6::Export::Attrs (
> https://metacpan.org/pod/Perl6::Export::Attrs) module, but with the
> following differences:
>
> - :export is lower case
> - User-specified export groups do not allow a colon (:export(strings,
> munging))
> - Built-in export behavior requires a colon prefix (:export(:MANDATORY)
> )
>
> Naturally, any subroutines without an :export tag are not available for
> export.
>
> Importing *any* group requires the colon:
>
> use Some::Module ':strings';
>
> Possibly we may require user-defined groups to not be all upper-case.
>
> *Backwards Compatibility*
>
> Currently, Amores' syntax is almost entirely backwards-compatible because
> the code does not parse on older Perls that use strict. This is helped
> tremendously by requiring a postfix block syntax which encapsulates the
> changes, rather than the package syntax for which a postfix block is
> optional.
>
> $ perl -Mstrict -Mwarnings -E 'module Foo { sub {...} }'
> Odd number of elements in anonymous hash at -e line 1.
> Can't locate object method "module" via package "Foo" ...
>
> Various incantations all cause the similar failures. If strict is not
> used, you will still get runtime failures with strange error messages due
> to indirect object syntax:
>
> $ perl -e 'module Foo { my $x }'
> Can't locate object method "module" via package "Foo" ...
>
> In an edge case, if have module Foo { ... } and you already have a class
> by that name defined (and loaded) elsewhere, then Perl will try an indirect
> object method call and that might succeed, leading to strange behavior:
>
> package Foo {
> sub module { print "darn it\n" }
> };
>
> module Foo {} # prints "darn it"
>
> *Tooling*
>
> As for tooling, we hope that B::Deparse (
> https://metacpan.org/pod/B::Deparse), Devel::Cover (
> https://metacpan.org/pod/Devel::Cover), and Devel::NYTProf (
> https://metacpan.org/pod/Devel::NYTProf), won't be impacted too strongly.
> However, this has not yet been tested, obviously.
>
> PPI (https://metacpan.org/pod/PPI) and dependent modules such as
> Perl::Critic (https://metacpan.org/pod/Perl::Critic) and friends) will be
> impacted.
>
> *Feature Guard*
>
> For newer Perl's, Amores will not be available by default. Instead, it
> will start with a feature guard:
>
> use feature 'module';
>
> module Some::Utils {
> ...
> }
>
> Later, it will likely be automatically available with use v8;
> (speculating about the version number).
>
> *Security Implications*
>
> Most of what we plan leverages Perl's current capabilities, but with a
> different grammar. We don't anticipate particular security issues. In fact,
> due to increased encapsulation, Amores might actually be a bit more secure
> due to its ability to limit behavior it exposes (assuming we remove the
> subs from a namespace).
>
> *Scope for future work*
>
> Plenty of scope, because we have a lexically scoped block to constrain
> changes, but for now, I think something small is the way to go.
>
>
> *Future: Inner Modules*
>
> It would be nice to see public and private inner modules which are hidden
> from the outside world. These would be analogous to the utility of inner
> classes.
>
> module Math {
> sub foo {
> # available to Math::Integer and Math::Float, but not exposed
> # publicly because it has no :export
> }
>
> module Integer { # exposed as Math::Integer
> ...
> }
> module Float { # exposed as Math::Float
> ...
> }
> module Some::Thing :private { # not exposed
> # I'm less sure about the utility vis-a-vis inner classes
> # because procedural code doesn't really have native
> requirements
> # for state, but it allows us to create utility modules
> # which are private
> }
> }
>
> Eventually, package could be used for older namespace declarations, while
> modules would be first-class and not necessarily exposed via the namespace
> mechanism unless they were public (or perhaps not even then).
>
> *Future: Script Support*
>
> Something like Raku's MAIN function (
> https://docs.raku.org/language/create-cli) would make it easier to write
> scripts directly, rather than choosing (and arguing over) myriad
> command-line handling functions.
>
> *Why not stick with package?*
>
> Because I want us to have something clearly analogous to Corinna in terms
> of structure (KIM syntax) and clearly something new to show movement rather
> than "here's a new feature we just tossed in." That last bit is important
> because I want a to avoid the issue of us just throwing random features at
> Perl independently without them being thought out holistically.
>
> I've thought about Amores for a long time and to be honest, it scratches
> my personal itches and it's not entirely fair to assume that everyone
> itches in the same spot. However, I've tried to ensure that this proposal
> is the simplest MVP that can be both effective and forward-looking and
> handles some of the worst nits of Perl.
>
> The syntax and intent of module mirrors the intent of Corinna's class,
> but clearly on a much smaller scale because the core of Perl procedural
> code is pretty solid, but fine-tuning for common defaults is useful. And by
> using postfix blocks, we minimize the risk of these features leaking into
> "legacy" code. And if this remains experimental for a while and we want to
> add new features (e.g., inner modules), we have a fresh, new playground to
> play with that doesn't break older code.
>
> I think Amores and Corinna together *greatly* improve the overall utility
> of Perl. Corinna makes modern OOP possible (and native), while Amores
> removes a number of warts. The actual value of Amores as a single proposal
> is marginal, but when coupled with Corinna, Perl starts moving towards a
> more consistent, modern future that's easier to reason about and teach
> (especially when using KIM to limit keywords).
>
Want to get my comments on this RFC down before I forget.
As in the previous discussion on this, I feel that there are some useful
feature requests here, but not a cohesive feature where a new feature flag
and keyword (especially such a heavily charged keyword) is warranted.
1. The :export feature, or some other standardized syntax to mark subs
exportable in an intentional design, would be a huge improvement over the
Exporter situation. I think this warrants an RFC and discussion on its own.
2. You haven't explained how requiring a block scope is beneficial (other
than the note in backwards compatibility, which doesn't seem a useful
distinction when the proposal is intended to be feature-gated). Lexical
declarations work perfectly fine without being attached to a block, and can
be surrounded by a block if you need one, and the option to write a module
as the entire contents of a file without an extra scope seems important to
preserve, popular, and exactly as featureful.
3. To wit, all of the features being enabled here are just an unversioned
feature bundle (in the use VERSION sense). This seems like something that
would quickly become an outdated featureset and interact confusingly with
feature bundles that already exist.
4. Version declarations in package statements are already-existing syntax.
I don't feel there's a strong case to add a second slightly different
version of it.
Putting this all together as you envision would be a fine job for a CPAN
module, but for a core feature it is a tough sell for me, apart from the
export mechanism.
-Dan
Thread Previous
|
Thread Next