Source profileQuality 85/100

affaan-m/ECC/skills/perl-patterns/SKILL.md

perl-patterns

Modern Perl 5.36+ idioms, best practices, and conventions for building robust, maintainable Perl applications.

Source repository stars
234,327
Declared platforms
0
Static risk flags
1
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Idiomatic Perl 5.36+ patterns and best practices for building robust, maintainable applications.

Best for

    Not for

    • Remember: Modern Perl is clean, readable, and safe. Let use v5.36 handle the boilerplate, use Moo for objects, and prefer CPAN's battle-tested modules over hand-rolled solutions.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/perl-patterns"
    Safe inspection promptEditorial

    Inspect the Agent Skill "perl-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/perl-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Usage

      my $user = User-new( name = 'Alice', email = 'alice@example.com', roles = ['admin', 'user'], );

      my $user = User-new( name = 'Alice', email = 'alice@example.com', roles = ['admin', 'user'], );
    2. 02

      When to Activate

      Writing new Perl code or modules

      Writing new Perl code or modulesReviewing Perl code for idiom complianceRefactoring legacy Perl to modern standards
    3. 03

      How It Works

      Apply these patterns as a bias toward modern Perl 5.36+ defaults: signatures, explicit modules, focused error handling, and testable boundaries. The examples below are meant to be copied as starting points, then tightened for the actual app, dependency stack, and deployment mode…

      Apply these patterns as a bias toward modern Perl 5.36+ defaults: signatures, explicit modules, focused error handling, and testable boundaries. The examples below are meant to be copied as starting points, then tighten…
    4. 04

      Core Principles

      A single use v5.36 replaces the old boilerplate and enables strict, warnings, and subroutine signatures.

      A single use v5.36 replaces the old boilerplate and enables strict, warnings, and subroutine signatures.
    5. 05

      1. Use v5.36 Pragma

      A single use v5.36 replaces the old boilerplate and enables strict, warnings, and subroutine signatures.

      A single use v5.36 replaces the old boilerplate and enables strict, warnings, and subroutine signatures.

    Permission review

    Static risk signals and limitations

    Reads files

    low · line 458

    The documentation asks the agent to read local files, directories, or repositories.

    | `open FH, "< $file"` | `open my $fh, '<:encoding(UTF-8)', $file` |

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score85/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/perl-patterns/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Modern Perl Development Patterns

    Idiomatic Perl 5.36+ patterns and best practices for building robust, maintainable applications.

    When to Activate

    • Writing new Perl code or modules
    • Reviewing Perl code for idiom compliance
    • Refactoring legacy Perl to modern standards
    • Designing Perl module architecture
    • Migrating pre-5.36 code to modern Perl

    How It Works

    Apply these patterns as a bias toward modern Perl 5.36+ defaults: signatures, explicit modules, focused error handling, and testable boundaries. The examples below are meant to be copied as starting points, then tightened for the actual app, dependency stack, and deployment model in front of you.

    Core Principles

    1. Use v5.36 Pragma

    A single use v5.36 replaces the old boilerplate and enables strict, warnings, and subroutine signatures.

    # Good: Modern preamble
    use v5.36;
    
    sub greet($name) {
        say "Hello, $name!";
    }
    
    # Bad: Legacy boilerplate
    use strict;
    use warnings;
    use feature 'say', 'signatures';
    no warnings 'experimental::signatures';
    
    sub greet {
        my ($name) = @_;
        say "Hello, $name!";
    }
    

    2. Subroutine Signatures

    Use signatures for clarity and automatic arity checking.

    use v5.36;
    
    # Good: Signatures with defaults
    sub connect_db($host, $port = 5432, $timeout = 30) {
        # $host is required, others have defaults
        return DBI->connect("dbi:Pg:host=$host;port=$port", undef, undef, {
            RaiseError => 1,
            PrintError => 0,
        });
    }
    
    # Good: Slurpy parameter for variable args
    sub log_message($level, @details) {
        say "[$level] " . join(' ', @details);
    }
    
    # Bad: Manual argument unpacking
    sub connect_db {
        my ($host, $port, $timeout) = @_;
        $port    //= 5432;
        $timeout //= 30;
        # ...
    }
    

    3. Context Sensitivity

    Understand scalar vs list context — a core Perl concept.

    use v5.36;
    
    my @items = (1, 2, 3, 4, 5);
    
    my @copy  = @items;            # List context: all elements
    my $count = @items;            # Scalar context: count (5)
    say "Items: " . scalar @items; # Force scalar context
    

    4. Postfix Dereferencing

    Use postfix dereference syntax for readability with nested structures.

    use v5.36;
    
    my $data = {
        users => [
            { name => 'Alice', roles => ['admin', 'user'] },
            { name => 'Bob',   roles => ['user'] },
        ],
    };
    
    # Good: Postfix dereferencing
    my @users = $data->{users}->@*;
    my @roles = $data->{users}[0]{roles}->@*;
    my %first = $data->{users}[0]->%*;
    
    # Bad: Circumfix dereferencing (harder to read in chains)
    my @users = @{ $data->{users} };
    my @roles = @{ $data->{users}[0]{roles} };
    

    5. The isa Operator (5.32+)

    Infix type-check — replaces blessed($o) && $o->isa('X').

    use v5.36;
    if ($obj isa 'My::Class') { $obj->do_something }
    

    Error Handling

    eval/die Pattern

    use v5.36;
    
    sub parse_config($path) {
        my $content = eval { path($path)->slurp_utf8 };
        die "Config error: $@" if $@;
        return decode_json($content);
    }
    

    Try::Tiny (Reliable Exception Handling)

    use v5.36;
    use Try::Tiny;
    
    sub fetch_user($id) {
        my $user = try {
            $db->resultset('User')->find($id)
                // die "User $id not found\n";
        }
        catch {
            warn "Failed to fetch user $id: $_";
            undef;
        };
        return $user;
    }
    

    Native try/catch (5.40+)

    use v5.40;
    
    sub divide($x, $y) {
        try {
            die "Division by zero" if $y == 0;
            return $x / $y;
        }
        catch ($e) {
            warn "Error: $e";
            return;
        }
    }
    

    Modern OO with Moo

    Prefer Moo for lightweight, modern OO. Use Moose only when its metaprotocol is needed.

    # Good: Moo class
    package User;
    use Moo;
    use Types::Standard qw(Str Int ArrayRef);
    use namespace::autoclean;
    
    has name  => (is => 'ro', isa => Str, required => 1);
    has email => (is => 'ro', isa => Str, required => 1);
    has age   => (is => 'ro', isa => Int, default  => sub { 0 });
    has roles => (is => 'ro', isa => ArrayRef[Str], default => sub { [] });
    
    sub is_admin($self) {
        return grep { $_ eq 'admin' } $self->roles->@*;
    }
    
    sub greet($self) {
        return "Hello, I'm " . $self->name;
    }
    
    1;
    
    # Usage
    my $user = User->new(
        name  => 'Alice',
        email => 'alice@example.com',
        roles => ['admin', 'user'],
    );
    
    # Bad: Blessed hashref (no validation, no accessors)
    package User;
    sub new {
        my ($class, %args) = @_;
        return bless \%args, $class;
    }
    sub name { return $_[0]->{name} }
    1;
    

    Moo Roles

    package Role::Serializable;
    use Moo::Role;
    use JSON::MaybeXS qw(encode_json);
    requires 'TO_HASH';
    sub to_json($self) { encode_json($self->TO_HASH) }
    1;
    
    package User;
    use Moo;
    with 'Role::Serializable';
    has name  => (is => 'ro', required => 1);
    has email => (is => 'ro', required => 1);
    sub TO_HASH($self) { { name => $self->name, email => $self->email } }
    1;
    

    Native class Keyword (5.38+, Corinna)

    use v5.38;
    use feature 'class';
    no warnings 'experimental::class';
    
    class Point {
        field $x :param;
        field $y :param;
        method magnitude() { sqrt($x**2 + $y**2) }
    }
    
    my $p = Point->new(x => 3, y => 4);
    say $p->magnitude;  # 5
    

    Regular Expressions

    Named Captures and /x Flag

    use v5.36;
    
    # Good: Named captures with /x for readability
    my $log_re = qr{
        ^ (?<timestamp> \d{4}-\d{2}-\d{2} \s \d{2}:\d{2}:\d{2} )
        \s+ \[ (?<level> \w+ ) \]
        \s+ (?<message> .+ ) $
    }x;
    
    if ($line =~ $log_re) {
        say "Time: $+{timestamp}, Level: $+{level}";
        say "Message: $+{message}";
    }
    
    # Bad: Positional captures (hard to maintain)
    if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\[(\w+)\]\s+(.+)$/) {
        say "Time: $1, Level: $2";
    }
    

    Precompiled Patterns

    use v5.36;
    
    # Good: Compile once, use many
    my $email_re = qr/^[A-Za-z0-9._%+-]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
    
    sub validate_emails(@emails) {
        return grep { $_ =~ $email_re } @emails;
    }
    

    Data Structures

    References and Safe Deep Access

    use v5.36;
    
    # Hash and array references
    my $config = {
        database => {
            host => 'localhost',
            port => 5432,
            options => ['utf8', 'sslmode=require'],
        },
    };
    
    # Safe deep access (returns undef if any level missing)
    my $port = $config->{database}{port};           # 5432
    my $missing = $config->{cache}{host};           # undef, no error
    
    # Hash slices
    my %subset;
    @subset{qw(host port)} = @{$config->{database}}{qw(host port)};
    
    # Array slices
    my @first_two = $config->{database}{options}->@[0, 1];
    
    # Multi-variable for loop (experimental in 5.36, stable in 5.40)
    use feature 'for_list';
    no warnings 'experimental::for_list';
    for my ($key, $val) (%$config) {
        say "$key => $val";
    }
    

    File I/O

    Three-Argument Open

    use v5.36;
    
    # Good: Three-arg open with autodie (core module, eliminates 'or die')
    use autodie;
    
    sub read_file($path) {
        open my $fh, '<:encoding(UTF-8)', $path;
        local $/;
        my $content = <$fh>;
        close $fh;
        return $content;
    }
    
    # Bad: Two-arg open (shell injection risk, see perl-security)
    open FH, $path;            # NEVER do this
    open FH, "< $path";        # Still bad — user data in mode string
    

    Path::Tiny for File Operations

    use v5.36;
    use Path::Tiny;
    
    my $file = path('config', 'app.json');
    my $content = $file->slurp_utf8;
    $file->spew_utf8($new_content);
    
    # Iterate directory
    for my $child (path('src')->children(qr/\.pl$/)) {
        say $child->basename;
    }
    

    Module Organization

    Standard Project Layout

    MyApp/
    ├── lib/
    │   └── MyApp/
    │       ├── App.pm           # Main module
    │       ├── Config.pm        # Configuration
    │       ├── DB.pm            # Database layer
    │       └── Util.pm          # Utilities
    ├── bin/
    │   └── myapp                # Entry-point script
    ├── t/
    │   ├── 00-load.t            # Compilation tests
    │   ├── unit/                # Unit tests
    │   └── integration/         # Integration tests
    ├── cpanfile                 # Dependencies
    ├── Makefile.PL              # Build system
    └── .perlcriticrc            # Linting config
    

    Exporter Patterns

    package MyApp::Util;
    use v5.36;
    use Exporter 'import';
    
    our @EXPORT_OK   = qw(trim);
    our %EXPORT_TAGS = (all => \@EXPORT_OK);
    
    sub trim($str) { $str =~ s/^\s+|\s+$//gr }
    
    1;
    

    Tooling

    perltidy Configuration (.perltidyrc)

    -i=4        # 4-space indent
    -l=100      # 100-char line length
    -ci=4       # continuation indent
    -ce         # cuddled else
    -bar        # opening brace on same line
    -nolq       # don't outdent long quoted strings
    

    perlcritic Configuration (.perlcriticrc)

    severity = 3
    theme = core + pbp + security
    
    [InputOutput::RequireCheckedSyscalls]
    functions = :builtins
    exclude_functions = say print
    
    [Subroutines::ProhibitExplicitReturnUndef]
    severity = 4
    
    [ValuesAndExpressions::ProhibitMagicNumbers]
    allowed_values = 0 1 2 -1
    

    Dependency Management (cpanfile + carton)

    cpanm App::cpanminus Carton   # Install tools
    carton install                 # Install deps from cpanfile
    carton exec -- perl bin/myapp  # Run with local deps
    
    # cpanfile
    requires 'Moo', '>= 2.005';
    requires 'Path::Tiny';
    requires 'JSON::MaybeXS';
    requires 'Try::Tiny';
    
    on test => sub {
        requires 'Test2::V0';
        requires 'Test::MockModule';
    };
    

    Quick Reference: Modern Perl Idioms

    Legacy PatternModern Replacement
    use strict; use warnings;use v5.36;
    my ($x, $y) = @_;sub foo($x, $y) { ... }
    @{ $ref }$ref->@*
    %{ $ref }$ref->%*
    open FH, "< $file"open my $fh, '<:encoding(UTF-8)', $file
    blessed hashrefMoo class with types
    $1, $2, $3$+{name} (named captures)
    eval { }; if ($@)Try::Tiny or native try/catch (5.40+)
    BEGIN { require Exporter; }use Exporter 'import';
    Manual file opsPath::Tiny
    blessed($o) && $o->isa('X')$o isa 'X' (5.32+)
    builtin::true / falseuse builtin 'true', 'false'; (5.36+, experimental)

    Anti-Patterns

    # 1. Two-arg open (security risk)
    open FH, $filename;                     # NEVER
    
    # 2. Indirect object syntax (ambiguous parsing)
    my $obj = new Foo(bar => 1);            # Bad
    my $obj = Foo->new(bar => 1);           # Good
    
    # 3. Excessive reliance on $_
    map { process($_) } grep { validate($_) } @items;  # Hard to follow
    my @valid = grep { validate($_) } @items;           # Better: break it up
    my @results = map { process($_) } @valid;
    
    # 4. Disabling strict refs
    no strict 'refs';                        # Almost always wrong
    ${"My::Package::$var"} = $value;         # Use a hash instead
    
    # 5. Global variables as configuration
    our $TIMEOUT = 30;                       # Bad: mutable global
    use constant TIMEOUT => 30;              # Better: constant
    # Best: Moo attribute with default
    
    # 6. String eval for module loading
    eval "require $module";                  # Bad: code injection risk
    eval "use $module";                      # Bad
    use Module::Runtime 'require_module';    # Good: safe module loading
    require_module($module);
    

    Remember: Modern Perl is clean, readable, and safe. Let use v5.36 handle the boilerplate, use Moo for objects, and prefer CPAN's battle-tested modules over hand-rolled solutions.

    Alternatives

    Compare before choosing