Tag: exceptions

  • Porting from Perl to Go: Simplifying for Platform Engineering

    Porting from Perl to Go: Simplifying for Platform Engineering

    Rewriting a script for the Homebrew pack­age man­ag­er taught me how the Go pro­gram­ming lan­guage’s design choic­es align with platform-​ready tools.

    The problem with brew upgrade

    By default, the brew upgrade com­mand updates every for­mu­la (ter­mi­nal util­i­ty or library). It also updates every cask (GUI appli­ca­tion) it man­ages. All are upgrad­ed to the lat­est ver­sion — major, minor, and patch. That’s con­ve­nient when you want the newest fea­tures, but dis­rup­tive when you only want qui­et patch-​level fixes.

    Last week I solved this in Perl with brew-patch-upgrade.pl, a script that parsed brew upgrades JSON out­put, com­pared seman­tic ver­sions, and upgrad­ed only when the patch num­ber changed. It worked, but it also remind­ed me how much Perl leans on implic­it struc­tures and run­time flexibility.

    This week I port­ed the script to Go, the lin­gua fran­ca of DevOps. The goal was­n’t fea­ture par­i­ty — it was to see how Go’s design choic­es map onto plat­form engi­neer­ing concerns.

    Why port to Go?

    • Portfolio prac­tice: I’m build­ing a body of work that demon­strates plat­form engi­neer­ing skills.
    • Operational focus: Go is wide­ly used for tool­ing in infra­struc­ture and cloud environments.
    • Learning by con­trast: Rewriting a work­ing Perl script in Go forces me to con­front dif­fer­ences in error han­dling, type safe­ty, and distribution.

    The journey

    Error handling philosophy

    Perl gave me try/​catch (exper­i­men­tal in the Perl v5.34.1 that ships with macOS, but since accept­ed into the lan­guage in v5.40). Go, famous­ly, does not. Instead, every func­tion returns an error explicitly.

    Perl:

    use v5.34;
    use warnings;
    use experimental qw(try);
    use Carp;
    use autodie;
    
    ...
    
    try {
      system 'brew', 'upgrade', $name;
      $result = 'upgraded';
    }
    catch ($e) {
      $result = 'failed';
      carp $e;
    }

    Go:

    package main
    
    import (
      "os/exec"
      "log"
    )
    
    ...
    
    cmd := exec.Command("brew", "upgrade", name)
    if output, err := cmd.CombinedOutput(); err != nil {
      log.Printf("failed to upgrade %s: %v\n%s",
        name,
        err,
        output)
    }

    The Go ver­sion is nois­i­er, but it forces explic­it deci­sions. That’s a fea­ture in pro­duc­tion tool­ing: no silent failures.

    Dependency management

    • Perl: cpanfile + CPAN mod­ules. Distribution means install Perl (if it’s not already), install mod­ules, run script.” Tools like carton and the cpan or cpanm com­mands help auto­mate this. Additionally, one can use fur­ther tool­ing like fatpack and pp to build more self-​contained pack­ages. But those are nei­ther com­mon nor (except for cpan) dis­trib­uted with Perl.
    • Go: go.mod + go build. Distribution is a sin­gle (platform-​specific) binary.

    For oper­a­tional tools, that’s a mas­sive sim­pli­fi­ca­tion. No run­time inter­preter, no depen­den­cy dance.

    Type safety

    Perl let me parse JSON into hashrefs and trust the keys exist. Go required a struct:

    type Formula struct {
      Name              string   `json:"name"`
      CurrentVersion    string   `json:"current_version"`
      InstalledVersions []string `json:"installed_versions"`
    }

    The com­pil­er enforces assump­tions that Perl left implic­it. That fric­tion is valu­able — it sur­faces errors early.

    Binary distribution

    This is where Go shines. Instead of telling col­leagues install Perl v5.34 and CPAN mod­ules,” I can hand them a bina­ry. No need to wor­ry about script­ing run­time envi­ron­ments — just grab the right file for your system.

    Available on the release page. Download, run, done.

    Semantic versioning logic

    In Perl, I man­u­al­ly com­pared arrays of ver­sion num­bers. In Go, I import­ed golang.org/x/mod/semver:

    import (
      golang.org/x/mod/semver
    )
    
    ...
    
    if semver.MajorMinor(toSemver(formula.InstalledVersions[0])) !=
      semver.MajorMinor(toSemver(formula.CurrentVersion)) {
      log.Printf("%s is not a patch upgrade", formula.Name)
      results.skipped++
      continue
    }

    Cleaner, more leg­i­ble, and less error-​prone. The library encodes the con­ven­tion, so I don’t have to.

    Deliberate simplification

    I did­n’t port every fea­ture. Logging adapters, sig­nal han­dlers, and edge-​case diag­nos­tics remained in Perl. The Go ver­sion focus­es on the core log­ic: parse JSON, com­pare ver­sions, run upgrades. That restraint was inten­tion­al — I want­ed to learn Go’s idioms, not repli­cate every Perl flourish.

    Platform engineering insights

    Three lessons stood out:

    1. Binary dis­tri­b­u­tion mat­ters. Operational tools should be instal­lable with a sin­gle copy step. Go makes that trivial.
    2. Semantic ver­sion­ing is an oper­a­tional prac­tice. It’s not just a con­ven­tion for library authors — it’s a con­tract that tool­ing can enforce.
    3. Go’s design aligns with plat­form needs. Explicit errors, type safe­ty, and sta­t­ic bina­ries all reduce sur­pris­es in production.

    Bringing it home

    This isn’t a Perl vs. Go” sto­ry. It’s a sto­ry about delib­er­ate sim­pli­fi­ca­tion, tak­ing a work­ing Perl script and recast­ing it in Go. The aim is to see how the lan­guage’s choic­es shape a solu­tion to the same problem.

    The result is homebrew-semver-guard v0.1.0, a small but stur­dy tool. It’s not feature-​finished, but it’s production-​ready in the ways that matter.

    Next up: I’m con­sid­er­ing more Go tools, maybe even Kubernetes for ser­vices on my home serv­er. This port was prac­tice, an arti­fact demon­strat­ing plat­form engi­neer­ing in action.


    Links

  • Get out early with Perl statement modifiers

    Get out early with Perl statement modifiers

    When I first start­ed writ­ing Perl in my ear­ly 20’s, I tend­ed to fol­low a lot of the struc­tured pro­gram­ming con­ven­tions I had learned in school through Pascal, espe­cial­ly the notion that every func­tion has a sin­gle point of exit. For example:

    sub double_even_number {
        # not using signatures, this is mid-1990's code
        my $number = shift;
    
        if (not $number % 2) {
            $number *= 2;
        }
    
        return $number; 
    }

    This could get pret­ty con­vo­lut­ed, espe­cial­ly if I was doing some­thing like val­i­dat­ing mul­ti­ple argu­ments. And at the time I didn’t yet grok how to han­dle excep­tions with eval and die, so I’d end up with code like:

    sub print_postal_address {
        # too many arguments, I know
        my ($name, $street1, $street2, $city, $state, $zip) = @_;
        # also this notion of addresses is naive and US-centric
    
        my $error;
    
        if (!$name) {
            $error = 'no name';
        }
        else {
            print "$name\n";
    
            if (!$street1) {
                $error = 'no street';
            }
            else {
                print "$street1\n";
    
                if ($street2) {
                    print "$street2\n";
                }
    
                if (!$city) {
                    $error = 'no city';
                }
                else {
                    print "$city, ";
    
                    if (!$state) {
                        $error = 'no state';
                    }
                    else {
                        print "$state ";
    
                        if (!$zip) {
                            $error = 'no ZIP code';
                        }
                        else {
                            print "$zip\n";
                        }
                    }
                }
            }
        }
    
        return $error;
    }

    What a mess. Want to count all those braces to make sure they’re bal­anced? This is some­times called the arrow anti-​pattern, with the arrowhead(s) being the most nest­ed state­ment. The default ProhibitDeepNests perlcritic pol­i­cy is meant to keep you from doing that.

    The way out (lit­er­al­ly) is guard claus­es: check­ing ear­ly if some­thing is valid and bail­ing out quick­ly if not. The above exam­ple could be written:

    sub print_postal_address {
        my ($name, $street1, $street2, $city, $state, $zip) = @_;
    
        if (!$name) {
            return 'no name';
        }
        if (!$street1) {
            return 'no street1';
        }
        if (!$city) {
            return 'no city';
        }
        if (!$state) {
            return 'no state';
        }
        if (!$zip) {
            return 'no zip';
        }
    
        print join "\n",
          $name,
          $street1,
          $street2 ? $street2 : (),
          "$city, $state $zip\n";
    
        return;
    }

    With Perl’s state­ment mod­i­fiers (some­times called post­fix con­trols) we can do even better:

        ...
    
        return 'no name'    if !$name;
        return 'no street1' if !$street1;
        return 'no city'    if !$city;
        return 'no state'   if !$state;
        return 'no zip'     if !$zip;
    
        ...

    (Why if instead of unless? Because the lat­ter can be con­fus­ing with double-​negatives.)

    Guard claus­es aren’t lim­it­ed to the begin­nings of func­tions or even exit­ing func­tions entire­ly. Often you’ll want to skip or even exit ear­ly con­di­tions in a loop, like this exam­ple that process­es files from stan­dard input or the com­mand line:

    while (<>) {
        next if /^SKIP THIS LINE: /;
        last if /^END THINGS HERE$/;
    
        ...
    }

    Of course, if you are val­i­dat­ing func­tion argu­ments, you should con­sid­er using actu­al sub­rou­tine sig­na­tures if you have a Perl new­er than v5.20 (released in 2014), or one of the oth­er type val­i­da­tion solu­tions if not. Today I would write that postal func­tion like this, using Type::Params for val­i­da­tion and named arguments:

    use feature qw(say state); 
    use Types::Standard 'Str';
    use Type::Params 'compile_named';
    
    sub print_postal_address {
        state $check = compile_named(
            name    => Str,
            street1 => Str,
            street2 => Str, {optional => 1},
            city    => Str,
            state   => Str,
            zip     => Str,
        );
        my $arg = $check->(@_);
    
        say join "\n",
          $arg->{name},
          $arg->{street1},
          $arg->{street2} ? $arg->{street2} : (),
          "$arg->{city}, $arg->{state} $arg->{zip}";
    
        return;
    }
    
    print_postal_address(
        name    => 'J. Random Hacker',
        street1 => '123 Any Street',
        city    => 'Somewhereville',
        state   => 'TX',
        zip     => 12345,
    );

    Note that was this part of a larg­er pro­gram, I’d wrap that print_postal_address call in a try block and catch excep­tions such as those thrown by the code ref­er­ence $check gen­er­at­ed by compile_named. This high­lights one con­cern of guard claus­es and oth­er return ear­ly” pat­terns: depend­ing on how much has already occurred in your pro­gram, you may have to per­form some resource cleanup either in a catch block or some­thing like Syntax::Keyword::Try’s finally block if you need to tidy up after both suc­cess and failure.

  • Highlighting members of the Perl family

    Highlighting members of the Perl family

    This past year of blog­ging has intro­duced me to a wide vari­ety of peo­ple in the Perl com­mu­ni­ty. Some I’ve admired from afar for years due to their pub­lished work, and even more I’ve met” inter­act­ing on social media and oth­er forums. So this will be the first in an occa­sion­al series high­light­ing not just the code, but the peo­ple that make up the Perl family.

    Paul LeoNerd” Evans

    I first came across Paul’s work dur­ing his series last year on writ­ing a core Perl fea­ture; he’s respon­si­ble for Perl v5.32’s isa oper­a­tor and v5.34’s exper­i­men­tal try/​catch excep­tion han­dling syn­tax. I inter­viewed him about the lat­ter for Perl.com in March 2021. He’s been active on CPAN for so much longer, though, and joined the Perl Steering Council in July. He’s also often a help­ful voice on IRC.

    Elliot Holden

    Renowned author and train­er Randal L. mer­lyn” Schwartz linked over the week­end in a pri­vate Facebook group to Elliot’s impas­sioned YouTube video about his day job as a Perl web appli­ca­tion devel­op­er. Through his alter ego Urban Guitar Legend Elliot is also a pas­sion­ate musi­cian; besides gig­ging and record­ing he’s been post­ing videos for nine years. (I’m a bit envi­ous since I took a break from music almost twen­ty years ago and haven’t man­aged to recap­ture it.) Elliot seems like the quin­tes­sen­tial needs-​to-​get-​shit-​done devel­op­er, and Perl is per­fect for that.

    Gábor Szabó

    Gábor is a poly­glot (both in human and com­put­er lan­guages) train­er, con­sul­tant, and author, writ­ing about pro­gram­ming and devops on his Code Maven and Perl Maven web­sites. He’s also the founder and co-​editor of Perl Weekly and recip­i­ent of a Perl White Camel award in 2008 thanks to his orga­ni­za­tion­al and sup­port con­tri­bu­tions. Last year he intro­duced me to the world of live pair pro­gram­ming, work­ing on a web appli­ca­tion using the Mojolicious frame­work.


    If you’re on Twitter and look­ing to con­nect with oth­er Perl devel­op­ers, please con­sid­er par­tic­i­pat­ing in the Perl com­mu­ni­ty I’ve set up there. Twitter Communities are topic-​specific mod­er­at­ed dis­cus­sion groups, unlike the free­wheel­ing #hash­tags sys­tem that can be dilut­ed by spam or top­ics that share the same name. Unfortunately, they’re still read-​only on the Twitter Android app, but you can par­tic­i­pate ful­ly on iOS/​iPadOS and the web­site.

  • Perl warnings and the warn function

    Perl warnings and the warn function

    I men­tioned in pass­ing last week that the next major release of Perl, v5.36, is set to enable warnings by 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-​grained since its intro­duc­tion in 2000. And fel­low Perl blog­ger and CPAN author Tom Wyant has 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 warnings prag­ma and the relat­ed warn func­tion for report­ing arbi­trary run­time errors. warn out­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_ERROR under use English) fol­lowed by a tab and then “...caught at <file> line x.” If that’s emp­ty too, a plain warn just 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.

    warn out­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 %SIG hash. 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 BEGIN block, 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­ule and 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 could use 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 else to suit your needs.

  • Avoid Yoda conditions in Perl you should

    Avoid Yoda conditions in Perl you should

    I remem­ber a brief time in the mid-​2000s insist­ing on so-​called Yoda con­di­tions” in my Perl. I would place con­stants to the left of equal­i­ty com­par­isons. In case I acci­den­tal­ly typed a sin­gle = instead of ==, the com­pil­er would catch it instead of blithe­ly assign­ing a vari­able. E.g.:

    if ( $foo == 42 ) { ... } # don’t do this
    if ( 42 == $foo ) { ... } # do this
    if ( $foo = 42  ) { ... } # to prevent this

    And because a fool­ish con­sis­ten­cy is the hob­gob­lin of lit­tle minds, I would even extend this to string and rela­tion­al comparisons.

    if ( 'bar' eq $foo ) { ... } # weirdo
    if ( 42 > $foo )     { ... } # make it stop

    It looks weird, and it turns out it’s unnec­es­sary as long as you pre­cede your code with use warnings;. Perl will then warn you: Found = in conditional, should be ==“. (Sidenote: Perl v5.36, due in mid-​2022, is slat­ed to enable warn­ings by default if you do use v5.35; or above, in addi­tion to the strict­ness that was enabled with use v5.11;. Yay for less boilerplate!)

    If you want to fatal­ly catch this and many oth­er warn­ings, use the stric­tures mod­ule from CPAN in your code like this:

    use strictures 2;

    This will cause your code to throw an excep­tion if it com­mits many cat­e­gories of mis­takes. If you’re run­ning in a ver­sion con­trol sys­tem’s work­ing direc­to­ry (specif­i­cal­ly Git, Subversion, Mercurial, or Bazaar), the mod­ule also pre­vents you from using indi­rect object syn­tax, Perl 4‑style mul­ti­di­men­sion­al arrays, and bare­word file­han­dles.

    Getting back to assign­ments vs. con­di­tion­als, there is one case where I’ve found it to be accept­able to use an assign­ment inside an if state­ment, and that’s when I need to use the result of a check inside the con­di­tion. For example:

    if ( my $foo = some_truthy_function() ) {
        ... # do something further with $foo
    }

    This keeps the scope of some_truthy_function()s result inside the block so that I don’t pol­lute the out­er scope with a tem­po­rary vari­able. Fortunately, Perl does­n’t warn on this syntax.