Claude Agent Skill · by Affaan M

Perl Patterns

Install Perl Patterns skill for Claude Code from affaan-m/everything-claude-code.

Works with Paperclip

How Perl Patterns fits into a Paperclip company.

Perl Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md504 lines
Expand
---name: perl-patternsdescription: Modern Perl 5.36+ idioms, best practices, and conventions for building robust, maintainable Perl applications.origin: ECC--- # 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. ```perl# Good: Modern preambleuse v5.36; sub greet($name) {    say "Hello, $name!";} # Bad: Legacy boilerplateuse 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. ```perluse v5.36; # Good: Signatures with defaultssub 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 argssub log_message($level, @details) {    say "[$level] " . join(' ', @details);} # Bad: Manual argument unpackingsub connect_db {    my ($host, $port, $timeout) = @_;    $port    //= 5432;    $timeout //= 30;    # ...}``` ### 3. Context Sensitivity Understand scalar vs list context — a core Perl concept. ```perluse v5.36; my @items = (1, 2, 3, 4, 5); my @copy  = @items;            # List context: all elementsmy $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. ```perluse v5.36; my $data = {    users => [        { name => 'Alice', roles => ['admin', 'user'] },        { name => 'Bob',   roles => ['user'] },    ],}; # Good: Postfix dereferencingmy @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')`. ```perluse v5.36;if ($obj isa 'My::Class') { $obj->do_something }``` ## Error Handling ### eval/die Pattern ```perluse 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) ```perluse 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+) ```perluse 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. ```perl# Good: Moo classpackage 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; # Usagemy $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 ```perlpackage 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) ```perluse 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 ```perluse v5.36; # Good: Named captures with /x for readabilitymy $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 ```perluse v5.36; # Good: Compile once, use manymy $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 ```perluse v5.36; # Hash and array referencesmy $config = {    database => {        host => 'localhost',        port => 5432,        options => ['utf8', 'sslmode=require'],    },}; # Safe deep access (returns undef if any level missing)my $port = $config->{database}{port};           # 5432my $missing = $config->{cache}{host};           # undef, no error # Hash slicesmy %subset;@subset{qw(host port)} = @{$config->{database}}{qw(host port)}; # Array slicesmy @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 ```perluse 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 thisopen FH, "< $path";        # Still bad — user data in mode string``` ### Path::Tiny for File Operations ```perluse v5.36;use Path::Tiny; my $file = path('config', 'app.json');my $content = $file->slurp_utf8;$file->spew_utf8($new_content); # Iterate directoryfor my $child (path('src')->children(qr/\.pl$/)) {    say $child->basename;}``` ## Module Organization ### Standard Project Layout ```textMyApp/├── 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 ```perlpackage 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) ```text-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) ```iniseverity = 3theme = core + pbp + security [InputOutput::RequireCheckedSyscalls]functions = :builtinsexclude_functions = say print [Subroutines::ProhibitExplicitReturnUndef]severity = 4 [ValuesAndExpressions::ProhibitMagicNumbers]allowed_values = 0 1 2 -1``` ### Dependency Management (cpanfile + carton) ```bashcpanm App::cpanminus Carton   # Install toolscarton install                 # Install deps from cpanfilecarton exec -- perl bin/myapp  # Run with local deps``` ```perl# cpanfilerequires '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 Pattern | Modern 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 hashref` | `Moo` 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 ops | `Path::Tiny` || `blessed($o) && $o->isa('X')` | `$o isa 'X'` (5.32+) || `builtin::true / false` | `use builtin 'true', 'false';` (5.36+, experimental) | ## Anti-Patterns ```perl# 1. Two-arg open (security risk)open FH, $filename;                     # NEVER # 2. Indirect object syntax (ambiguous parsing)my $obj = new Foo(bar => 1);            # Badmy $obj = Foo->new(bar => 1);           # Good # 3. Excessive reliance on $_map { process($_) } grep { validate($_) } @items;  # Hard to followmy @valid = grep { validate($_) } @items;           # Better: break it upmy @results = map { process($_) } @valid; # 4. Disabling strict refsno strict 'refs';                        # Almost always wrong${"My::Package::$var"} = $value;         # Use a hash instead # 5. Global variables as configurationour $TIMEOUT = 30;                       # Bad: mutable globaluse constant TIMEOUT => 30;              # Better: constant# Best: Moo attribute with default # 6. String eval for module loadingeval "require $module";                  # Bad: code injection riskeval "use $module";                      # Baduse Module::Runtime 'require_module';    # Good: safe module loadingrequire_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.