develooper Front page | perl.perl5.porters | Postings from January 2022

RFC: Amores. Introducing a `module` keyword

Thread Next
From:
Ovid via perl5-porters
Date:
January 25, 2022 16:40
Subject:
RFC: Amores. Introducing a `module` keyword
Message ID:
1774505117.1120924.1643128831409@mail.yahoo.com
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).
Best,Ovid-- 
IT consulting, training, specializing in Perl, databases, and agile development
http://www.allaroundtheworld.fr/. 

Buy my book! - http://bit.ly/beginning_perl
Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About