Tag: CPAN

  • Logging from Perl to macOS’ unified log with FFI and Log::Any

    Logging from Perl to macOS’ unified log with FFI and Log::Any

    Part 1: The elephant in the room

    A few weeks ago, I start­ed host­ing my own Mastodon instanceon a Mac miniin my home office. I want­ed to join the social Fediverseon my own terms–but it did­n’t take long to notice bal­loon­ing disk usage. Cached media from oth­er users’ posts was pil­ing up fast.

    That got me think­ing: how do I track this growth before it gets out of hand?

    Logging seemed like the obvi­ous answer. On Unix and Linux sys­tems, it’s straight­for­ward enough. But on macOS, find­ing a native, main­tain­able solu­tion takes more digging.

    Part 2: Feeding the Apple

    macOS is Unix-​based, so you’d expect log­ging to be sim­ple. You caninstall logro­tatevia Homebrew, then sched­ule it with cron(8). It works–but it adds lay­ers of con­fig­u­ra­tion files, per­mis­sions, and guess­work. I want­ed some­thing native. Something that felt like it belonged on a Mac.

    Turns out, macOS offers twobuilt-​in options. One is newsys­log, a BSD-​style tool that rotates logs based on size or time. It’s reli­able, but it requires priv­i­leged root-owned con­fig­u­ra­tion files and feels like a holdover from old­er Unix systems.

    The oth­er is Apple’s uni­fied log­ging sys­tem–a mod­ern API used across macOS, iOS, and even watchOS. It’s struc­tured, search­able, and already baked into the plat­form. That’s the one I decid­ed to explore.

    Howard Oakley’s explain­er on the Unified Loghelped me under­stand Apple’s sys­tem for con­sol­i­dat­ing logs. It showed how they are stored in a com­pressed bina­ry for­mat, com­plete with struc­tured meta­da­ta and pri­va­cy con­trols. With that foun­da­tion, I turned to Apple’s OSLog Framework doc­u­men­ta­tion. It showed how to tag entries and fil­ter them with pred­i­cates. macOS han­dles the rest.

    It’s elegant–but you need to use the API to write logs. Yes, read­ing and fil­ter­ing can be done on the com­mand line or in the Console app. But Apple seems to expect log­ging to be the sole province of Swift and Objective‑C devel­op­ers. I’d rather not have to learn a new pro­gram­ming lan­guage just to write logs.

    UPDATE: Howard Oakley’s blow­holeutil­i­typro­vides a sim­ple way to write to the uni­fied log from the com­mand line, but all mes­sages come from the ​“co.eclecticlight.blowhole” sub­sys­tem with a ​“gen­er­al” cat­e­go­ry. We can do better.

    Part 3: A platypus in the key of C

    I doknow Perl. I also know just enough Cto be dan­ger­ous. And I briefly con­sid­ered learn­ing Swift or Objective‑C. Nevertheless, I won­dered about bridg­ing Perl to Apple’s uni­fied log­ging sys­tem with­out switch­ing languages.

    macOS expos­es a C API in <os/log.h>:

    #include <os/log.h>
    
    void
    os_log(os_log_t log, const char *format, ...);
    
    void
    os_log_info(os_log_t log, const char *format, ...);
    
    void
    os_log_debug(os_log_t log, const char *format, ...);
    
    void
    os_log_error(os_log_t log, const char *format, ...);
    
    void
    os_log_fault(os_log_t log, const char *format, ...);

    Perl’s CPANhas a mod­ule called FFI::Platypusthat would let me call for­eign func­tions in C and oth­er lan­guages. It looked promising.

    But there’s a catch: these log­ging func­tions are vari­adic macros, not plain func­tions. That makes them inac­ces­si­ble via FFI. Worse, they expand into pri­vate API calls–unstable across OS updates and risky to rely upon.

    So I wrote a small C wrap­per to con­vert each macro into a prop­er func­tion. This makes them FFI-​safe and lets me con­trol vis­i­bil­i­ty (pub­lic log­ging vs. pri­vate, redact­ed log­ging) using Apple’s for­mat specifiers:

    #include <os/log.h>
    
    #define DEFINE_OSLOG_WRAPPERS(level_macro, suffix)    \
        void os_log_##suffix##_public(os_log_t log,       \
                                      const char *msg) {  \
            level_macro(log, "%{public}s", msg);          \
        }                                                 \
        void os_log_##suffix##_private(os_log_t log,      \
                                       const char *msg) { \
            level_macro(log, "%{private}s", msg);         \
        }
    
    // Generate wrappers for each log level
    DEFINE_OSLOG_WRAPPERS(os_log, default)
    DEFINE_OSLOG_WRAPPERS(os_log_info, info)
    DEFINE_OSLOG_WRAPPERS(os_log_debug, debug)
    DEFINE_OSLOG_WRAPPERS(os_log_error, error)
    DEFINE_OSLOG_WRAPPERS(os_log_fault, fault)

    This macro gen­er­ates two func­tions per log level–one pub­lic, one private–giving down­stream Perl code a choice. It’s ver­bose, but it’s safe, auditable, and future-proof.

    Part 4: Plugging into Log::Any

    With the wrap­per library in place, I began map­ping Apple’s log lev­els to some­thing Perl can use. I chose Log::Any from CPANbecause it’s light­weight, wide­ly sup­port­ed, and its adapters don’t lock you into a spe­cif­ic back-​end. The same code that logs to the screen can also log to a file, or in our case, Apple’s system.

    Admittedly, at this point I’m no longer writ­ing a sim­ple log­ging script for my Mastodon instance. Instead, it’s a full-​fledged log­ging mod­ule. Oh well.

    Some Log::Any lev­elsshare the same under­ly­ing Apple call– OSLog does­n’t dis­tin­guish between noticeand infoor traceand debug. That’s a lit­tle dif­fer­ent from how Unix sys­logdoes things, but that’s fine. The goal here is com­pat­i­bil­i­ty, not per­fect fidelity.

    Building a sim­ple dis­patch table to route log mes­sages based on lev­el, I then used FFI::Platypus to bind each wrap­per function:

    use FFI::Platypus 2.00;
    
    my %OS_LOG_MAP = (
        trace     => 'os_log_debug',
        debug     => 'os_log_debug',
        info      => 'os_log_info',
        notice    => 'os_log_info',
        warning   => 'os_log_fault',
        error     => 'os_log_error',
        critical  => 'os_log_default',
        alert     => 'os_log_default',
        emergency => 'os_log_default',
    );
    
    my $ffi = FFI::Platypus->new(
        api => 2,
        lib => [ './liboslogwrapper.dylib' ],
    );
    
    $ffi->attach(
        [ os_log_create => '_os_log_create' ],
        [ 'string', 'string' ],
        'opaque',
    );
    
    # attach each wrapper function
    my %UNIQUE_OS_LOG = map { $_ => 1 } values %OS_LOG_MAP;
    foreach my $function ( keys %UNIQUE_OS_LOG ) {
        for my $variant (qw(public private)) {
            my $name = "${function}_$variant";
            $ffi->attach(
                [ $name => "_$name" ],
                [ 'opaque', 'string' ],
                'void',
            );
        }
    }

    This set­up gives me a clean way to log from Perl using Apple’s native sys­tem. I can achieve this with­out touch­ing Swift, Objective‑C, or exter­nal tools. Each log lev­el maps to a C wrap­per, and the FFI lay­er han­dles the rest.

    Now I just need an initfunc­tion to cre­ate the os_​log_​tobject and a set of meth­ods for log­ging and detect­ing whether a giv­en log lev­el is enabled:

    use strict;
    use Carp;
    use base qw(Log::Any::Adapter::Base);
    use Log::Any::Adapter::Util qw(
      detection_methods
      numeric_level
    );
    
    sub init {
        my $self = shift;
        $self->{private} ||= 0;
        croak 'subsystem is required'
          unless defined $self->{subsystem};
    
        $self->{_os_log} = _os_log_create(
          @{$self}{qw(subsystem category)},
        );
    
        return;
    }
    
    foreach my $log_level ( keys %OS_LOG_MAP ) {
        no strict 'refs';
        *{$log_level} = sub {
            my ( $self, $message ) = @_;
    
            &{  "_$OS_LOG_MAP{$log_level}_"
                    . ( $self->{private}
                        ? 'private'
                        : 'public'
                    ) }( $self->{_os_log}, $message );
        };
    }
    
    foreach my $method ( detection_methods() ) {
        my $method_level = numeric_level(substr $method 3);
        no strict 'refs';
        *{$method} = sub {
            !!( $method_level <= (
              $_[0]->{log_level} // numeric_level('info')
            ) );
        };
    }

    What’s that ​“sub­sys­tem” bit up there? That’s the term macOS uses for iden­ti­fy­ing process­es in logs. They’re usu­al­ly for­mat­ted in reverse DNS nota­tion (e.g., ​“com.example.perl”). Once again, Howard Oakley has a great explain­er on the top­ic.

    Also, there’s some metapro­gram­minggoing on there:

    • The first fore­achloop cre­ates func­tions called trace, debug, and info. These func­tions call the cor­re­spond­ing FFI::Platypus-created func­tions. It uses the pri­vate vari­ants if the pri­vateattribute for the log adapter was set.
    • The sec­ond fore­achloop cre­ates cre­ates func­tions called is_​trace, is_​debug, is_​info, etc., that return true if the adapter is catch­ing that lev­el of log message.

    Part 5: At long last, logging… mostly

    Once this is pack­aged in a Perl mod­ule, how do you use it? At least that part isn’t toohard:

    use Log::Any '$log', default_adapter => [
      'MacOS::OSLog', subsystem => 'com.phoenixtrap.perl',
    ];
    use English;
    use Carp qw(longmess);
    
    $log->info('Hello from Perl!');
    $log->infof('You are using Perl %s', $PERL_VERSION);
    
    $log->trace( longmess('tracing!') );
    $log->debug(     'debugging!'     );
    $log->info(      'informing!'     );
    $log->notice(    'noticing!'      );
    $log->warning(   'warning!'       );
    $log->error(     'erring!'        );
    $log->critical(  'critiquing!'    );
    $log->alert(     'alerting!'      );
    $log->emergency( 'emerging!'      );

    And then you can run this com­mand line to stream log mes­sages from the sub­sytem used above:

    % log stream --level debug \
      --predicate 'subsystem == "com.phoenixtrap.perl"

    What hap­pened to the traceand debuglog mes­sages that were sup­posed to call os_log_debug(3)? According to macOS’ log(1)man­u­al page, you have to explic­it­ly allow debug­ging out­putfor a giv­en subsystem:

    % sudo log config --mode "level:debug" \
      --subsystem com.phoenixtrap.perl

    Et voilà!

    Hmm, same lack of debug­ging messages.

    I’m still fig­ur­ing this out. Any clues? Drop me a line!

    UPDATE:This is now fixed thanks to some inspi­ra­tion from the source code of Log::Any::Adapter::Syslog. I’ve updat­ed the code on Codeberg; here is the diff.

    Bonus: Fancy output

    Thanks to Log::Any::Proxy, you also get sprintffor­mat­ting vari­ant functions:

    use English;
    $log->infof(
        'You are using Perl %s in %d',
        $PERL_VERSION, (localtime)[5] + 1900,
    );
    You are using Perl v5.40.2 in 2025

    If you out­put an object that over­loads string rep­re­sen­ta­tion, you get that string:

    use DateTime;
    $log->infof('It is now %s', DateTime->now);
    It is now 2025-08-10T20:16:50

    And you get single-​line Data::Dumperout­put of com­plex data struc­tures, plus replac­ing unde­fined val­ues with the string ​“undef”:

    $log->info( {
        foo    => 'hello',
        bar    => 'world',
        colors => [ qw(
            red
            green
            blue
        ) ],
        null => undef,
    } );
    {bar => "world",colors => ["red","green","blue"],foo => "hello",null => undef}

    Conclusion: Build once, use everywhere

    The best tools aren’t always the ones you planned to build. They’re the ones that solve a prob­lem cleanly–and then solve five more you hadn’t thought of yet.

    What start­ed as a quick fix for Mastodon media mon­i­tor­ing became a reusable bridge between Perl and macOS’ Unified Log. Along the way, I got to explore Apple’s log­ging inter­nals, write an FFI-​respecting C wrap­per, and inte­grate clean­ly with Log::Any. The result­ing code is mod­u­lar, auditable, and–most importantly–maintainable.

    I did­n’t set out to write a log­ging adapter. But when you care about clean ops and repro­ducible infra­struc­ture, some­times the best tools are the ones you build your­self. And if they hap­pen to be over-​engineered for the task at hand? All the better–they’ll prob­a­bly out­live it.

    Try it out or contribute!

    The full adapter code is on Codeberg.If you’re log­ging from Perl on macOS, give it a spin. Contributions, bug reports, and real-​world feed­back are welcome–especially if you’re test­ing it in pro­duc­tion or on old­er macOS versions.

    I’ll do my best to stay com­pat­i­ble with past and future macOS and Perl releas­es. Keeping the code auditable and min­i­mal should help it stay use­ful with­out becom­ing a mov­ing target.

  • MetaCPAN on Fastly—no Fastly on MetaCPAN?

    I’m tremen­dous­ly grate­ful for Fastly​’s sup­port of the Perl MetaCPANsearch engine over the past ten years, but the for­mer’s page says they only start­ed ​“serv­ing open source” for eight.
    logos: Rust, Python, Ruby, Scratch, LF, ASF, curl

    And I’m not sure how MetaCPAN is explic­it­ly part of this ini­tia­tive. I don’t see it mentioned.

  • How much is that BLÅHAJ in the (terminal) window?

    How much is that BLÅHAJ in the (terminal) window?

    IKEA’s toy BLÅHAJ sharkhas become a beloved Internet iconover the past sev­er­al years. I thought it might be cute to write a lit­tle Perl to get info about it and even dis­play a cud­dly pic­ture right in the ter­mi­nal where I’m run­ning the code. Maybe this will give you some ideas for your own quick web clients. Of course, you could accom­plish all of these things using a pipeline of indi­vid­ual command-​line util­i­ties like curl, jq, and GNU core­uti­ls’ base64. These exam­ples focus on Perl as the glue, though.

    Warning: dodgy API ahead

    I haven’t found a publicly-​documented and ‑sup­port­ed offi­cial API for query­ing IKEA prod­uct infor­ma­tion but oth­ers havedecon­struct­ed the company’s web site AJAXrequests so we can use that instead. The alter­na­tive would be to scrape the IKEA web site direct­ly which, although pos­si­ble, would be more tedious and prone to fail­ure should their design change. An unof­fi­cial API is also unre­li­able but the sim­pler client code is eas­i­er to change should any errors surface.

    Enter the Mojolicious

    My orig­i­nal goal was to do this in a sin­gle line issued to the perlcom­mand, and luck­i­ly the Mojolicious framework’s ojo mod­uleis tailor-​made for such things. By adding a -Mojoswitch to the perlcom­mand, you get over a dozen quick single-​character func­tions for spin­ning up a quick web appli­ca­tion or, in our case, mak­ing and inter­pret­ing web requests with­out a lot of cer­e­mo­ny. Here’s the start of my one-​line request to the IKEA API for infor­ma­tion on their BLÅHAJ prod­uct, using ojo’s gfunc­tionto per­form an HTTP GET and dis­play­ing the JSONfrom the response body to the terminal.

    $ perl -Mojo -E 'say g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->body'

    This cur­rent­ly returns over 2,400 lines of data, so after read­ing it over I’ll con­vert the response body JSON to a Perl data struc­tureand dump only the main prod­uct infor­ma­tion using ojo’s rfunc­tion:

    $ perl -Mojo -E 'say r g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}'
    {
      "availability" => [],
      "breathTaking" => bless( do{\(my $o = 0)}, 'JSON::PP::Boolean' ),
      "colors" => [
        {
          "hex" => "0058a3",
          "id" => 10007,
          "name" => "blue"
        },
        {
          "hex" => "ffffff",
          "id" => 10156,
          "name" => "white"
        }
      ],
      "contextualImageUrl" => "https://www.ikea.com/us/en/images/products/blahaj-soft-toy-shark__0877371_pe633608_s5.jpg",
      "currencyCode" => "USD",
      "discount" => "",
      "features" => [],
      "gprDescription" => {
        "numberOfVariants" => 0,
        "variants" => []
      },
      "id" => 90373590,
      "itemMeasureReferenceText" => "39 \x{bc} \"",
      "itemNo" => 90373590,
      "itemNoGlobal" => 30373588,
      "itemType" => "ART",
      "lastChance" => $VAR1->{"breathTaking"},
      "mainImageAlt" => "BL\x{c5}HAJ Soft toy, shark, 39 \x{bc} \"",
      "mainImageUrl" => "https://www.ikea.com/us/en/images/products/blahaj-soft-toy-shark__0710175_pe727378_s5.jpg",
      "name" => "BL\x{c5}HAJ",
      "onlineSellable" => bless( do{\(my $o = 1)}, 'JSON::PP::Boolean' ),
      "pipUrl" => "https://www.ikea.com/us/en/p/blahaj-soft-toy-shark-90373590/",
      "price" => {
        "decimals" => 99,
        "isRegularCurrency" => $VAR1->{"breathTaking"},
        "prefix" => "\$",
        "separator" => ".",
        "suffix" => "",
        "wholeNumber" => 19
      },
      "priceNumeral" => "19.99",
      "quickFacts" => [],
      "tag" => "NONE",
      "typeName" => "Soft toy"
    }

    If I just want the price I can do:

    $ perl -Mojo -E 'say g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}->@{qw(currencyCode priceNumeral)}'
    USD19.99

    That ->@{qw(currencyCode priceNumeral)}towards the end uses the post­fix ref­er­ence slic­ing syn­taxintro­duced exper­i­men­tal­ly in Perl v5.20and made offi­cial in v5.24. If you’re using an old­er perl, you’d say:

    $ perl -Mojo -E 'say @{g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}}{qw(currencyCode priceNumeral)}'
    USD19.99

    I pre­fer the for­mer, though, because it’s eas­i­er to read left-to-right.

    But I’m not in the United States! Where’s my native currency?

    You can either replace the ​”us/en” in the URL above or use the core I18N::LangTags::Detect mod­uleadded in Perl v5.8.5 if you’re real­ly deter­mined to be portable across dif­fer­ent users’ locales. This is real­ly stretch­ing the def­i­n­i­tion of ​”one-​liner,”though.

    $ LANG=de_DE.UTF-8 perl -Mojo -MI18N::LangTags::Detect -E 'my @lang = (split /-/, I18N::LangTags::Detect::detect)[1,0]; say g("https://sik.search.blue.cdtapps.com/" . join("/", @lang == 2 ? @lang : ("us", "en")) . "/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}->@{qw(currencyCode priceNumeral)}'
    EUR27.99

    Window dressing

    It’s hard to envi­sion cud­dling a num­ber, but luck­i­ly the prod­uct infor­ma­tion returned above links to a JPEG file in the mainImageUrlkey. My favorite ter­mi­nal app iTerm2 can dis­play images inlinefrom either a file or Base64encod­ed data, so adding an extra HTTP request and encod­ing from the core MIME::Base64 mod­uleyields:

    $ perl -Mojo -MMIME::Base64 -E 'say "\c[]1337;File=inline=1;width=100%:", encode_base64(g(g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}{mainImageUrl})->body), "\cG"'

    (You could just send the image URL to iTerm2’s bun­dled imgcatutil­i­ty, but where’s the fun in that?)

    $ imgcat --url `perl -Mojo -E 'print g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}{mainImageUrl}'`

    But I don’t have iTerm2 or a Mac!

    I got you. At the expense of a num­ber of oth­er depen­den­cies, here’s a ver­sion that will work on any ter­mi­nal that sup­ports 256-​color mode with ANSI codesusing Image::Term256Color from CPANand a Unicode font with block char­ac­ters. I’ll also use Term::ReadKeyto size the image for the width of your win­dow. (Again, this stretch­es the def­i­n­i­tion of ​“one-​liner.”)

    $ perl -Mojo -MImage::Term256Color -MTerm::ReadKey -E 'say for Image::Term256Color::convert(g(g("https://sik.search.blue.cdtapps.com/us/en/search-result-page", form => {types => "PRODUCT", q => "BLÅHAJ"})->json->{searchResultPage}{products}{main}{items}[0]{product}{mainImageUrl})->body, {scale_x => (GetTerminalSize)[0], utf8 => 1})'

    I hate Mojolicious! Can’t you just use core modules?

    Fine. Here’s retriev­ing the prod­uct price using HTTP::Tinyand the pure-​Perl JSON pars­er JSON::PP, which were added to core in ver­sion 5.14.

    $ perl -MHTTP::Tiny -MJSON::PP -E 'say @{decode_json(HTTP::Tiny->new->get("https://sik.search.blue.cdtapps.com/us/en/search-result-page?types=PRODUCT&q=BLÅHAJ")->{content})->{searchResultPage}{products}{main}{items}[0]{product}}{qw(currencyCode priceNumeral)}'
    USD19.99

    Fetching and dis­play­ing a pic­ture of the hug­gable shark using MIME::Base64 or Image::Term256Color as aboveis left as an exer­cise to the reader.

  • Perl list processing is for hashes, too

    Perl list processing is for hashes, too

    This month I start­ed a new job at Alert Logic, a cyber­se­cu­ri­ty provider with Perl (among many oth­er things) at its beat­ing heart. I’ve been learn­ing a lot, and part of the process has been under­stand­ing the APIs in the code base. To that end, I’ve been writ­ing small test scripts to tease apart data struc­tures, using Perl array-​processing, list-​processing, and hash- (i.e., asso­cia­tive array)-processing func­tions.

    I’ve cov­ered map, grep, and friends a cou­ple timesbefore. Most recent­ly, I described using List::Util’s anyfunc­tionto check if a con­di­tion is true for any item in a list. In the sim­plest case, you can use it to check to see if a giv­en val­ue is in the list at all:

    use feature 'say';
    use List::Util 'any';
    my @colors =
      qw(red orange yellow green blue indigo violet);
    say 'matched' if any { /^red$/ } @colors;

    However, if you’re going to be doing this a lot with arbi­trary strings, Perl FAQ sec­tion 4 advis­esturn­ing the array into the keys of a hash and then check­ing for mem­ber­ship there. For exam­ple, here’s a sim­ple script to check if the col­ors input (either from the key­board or from files passed as argu­ments) are in the rainbow:

    #!/usr/bin/env perl
    
    use v5.22; # introduced <<>> for safe opening of arguments
    use warnings;
     
    my %in_colors = map {$_ => 1}
      qw(red orange yellow green blue indigo violet);
    
    while (<<>>) {
      chomp;
      say "$_ is in the rainbow" if $in_colors{$_};
    }

    List::Util has a bunch of func­tions for pro­cess­ing lists of pairsthat I’ve found use­ful when paw­ing through hash­es. pairgrep, for exam­ple, acts just like grepbut instead assigns $aand $bto each key and val­ue passed in and returns the result­ing pairs that match. I’ve used it as a quick way to search for hash entries match­ing cer­tain val­ue conditions:

    use List::Util 'pairgrep';
    my %numbers = (zero => 0, one => 1, two => 2, three => 3);
    my %odds = pairgrep {$b % 2} %numbers;

    Sure, you could do this by invok­ing a mix of plain grep, keys, and a hash slice, but it’s nois­i­er and more repetitive:

    use v5.20; # for key/value hash slice 
    my %odds = %numbers{grep {$numbers{$_} % 2} keys %numbers};

    pairgrep​’s com­piled C‑based XScode can also be faster, as evi­denced by this Benchmarkscript that works through a hash made of the Unix wordsfile(479,828 entries on my machine):

    #!/usr/bin/env perl
    
    use v5.20;
    use warnings;
    use List::Util 'pairgrep';
    use Benchmark 'cmpthese';
    
    my (%words, $count);
    open my $fh, '<', '/usr/share/dict/words'
      or die "can't open words: $!";
    while (<$fh>) {
      chomp;
      $words{$_} = $count++;
    }
    close $fh;
    
    cmpthese(100, {
      grep => sub {
        my %odds = %words{grep {$words{$_} % 2} keys %words};
      },
      pairgrep => sub {
        my %odds = pairgrep {$b % 2} %words;
      },
    } );

    Benchmark out­put:

               Rate     grep pairgrep
    grep     1.47/s       --     -20%
    pairgrep 1.84/s      25%       --

    In gen­er­al, I urge you to work through the Perl doc­u­men­ta­tion​’s tuto­ri­als on ref­er­ences, lists of lists, the data struc­tures cook­book, and the FAQs on arrayand hash manip­u­la­tion. Then dip into the var­i­ous list-​processing mod­ules(espe­cial­ly the includ­ed List::Utiland CPAN’s List::SomeUtils) for ready-​made func­tions for com­mon oper­a­tions. You’ll find a wealth of tech­niques for cre­at­ing, man­ag­ing, and pro­cess­ing the data struc­tures that your pro­grams need.

  • Perl warnings and the warn function

    Perl warnings and the warn function

    I men­tioned in pass­ing last weekthat the next major release of Perl, v5.36, is set to enable warningsby default for code that opts in to use v5.35;or above. Commemorating Perl’s 34th birth­day the week before that, I not­ed that the warn­ings sys­tem has been get­ting ever finer-​grainedsince its intro­duc­tion in 2000. And fel­low Perl blog­ger and CPAN author Tom Wyanthas been cat­a­loging his favorites over the past sev­er­al months—the lat­est as of this writ­ing was on the ​“ambigu­ous” cat­e­go­ry of warn­ings, and you can find links to pre­vi­ous entries in his series at the bot­tom of that post.

    It occurred to me after­ward that there may be some con­fu­sion between the warningsprag­ma and the relat­ed warnfunc­tionfor report­ing arbi­trary run­time errors. warnout­puts its argu­ments to the stan­dard error (STDERR) stream, or if it’s not giv­en any then you get a string with any excep­tion from $@($EVAL_ERRORunder use English) fol­lowed by a tab and then “...caught at <file> line x.” If that’s emp­ty too, a plain warnjust says, ​“Warning: something's wrong at <file> line x.”, which isn’t exact­ly help­ful, but then again you didn’t give it much to go on.

    warnout­put doesn’t have to go to STDERR, and this is where the rela­tion to the warn­ings prag­ma comes in because both are gov­erned by the __WARN__sig­nal han­dler in the %SIGhash. Normally, you might opt to only dis­play run­time warn­ings if a debug­ging flag is set, like so:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    my $DEBUG = 0;
    $SIG{__WARN__} = sub { warn @_ if $DEBUG };
    warn 'shhh'; # silenced
    
    $DEBUG = 1;
    warn 'hello warnings';

    But if you set that sig­nal han­dler in a BEGINblock, it catch­es compile-​time warn­ings too, in which case flip­ping a flag after the fact has no effect—the compiler’s already run:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    my $DEBUG = 0;
    BEGIN { $SIG{__WARN__} = sub { warn @_ if $DEBUG } }
    my $foo = 'hello';
    my $foo = 'world'; # no warning issued here
    
    $DEBUG = 1;
    my $foo = 'howdy'; # still nothing

    By the way, both __WARN__and __DIE__hooks are also used by the Carp mod­uleand its friends, so you can use the same tech­nique with their enhanced output:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    use Carp qw(carp cluck);
    
    my $DEBUG = 0;
    BEGIN { $SIG{__WARN__} = sub { warn @_ if $DEBUG } }
    carp 'quiet fish';
    
    $DEBUG = 1;
    loud_chicken();
    
    sub loud_chicken {
        cluck 'here comes a stack trace';
    }

    You coulduse these as step­ping stones towards a debug log for larg­er appli­ca­tions, but at that point, I’d sug­gest look­ing into one of the log­ging mod­ules on CPAN like Log::Log4perl(not to be con­fused with that lately-​problematic Java library), Log::Dispatch(which can be wired into Log4perl), or some­thing elseto suit your needs.