cpan/Time-Piece - Update to version 1.38 1.38 2025-10-18 - Doc updates - Fix Windows-2025 crash with %P - XS clean up - strptime: %z and %Z fixes (GH52,32,73,RT93095,168828)
![]() | We're recording the Community Roundtable meetings now, so you can find out what's going on around the Perl and Raku communities. Our community isn't a single, monolithic entity, but an archipelago of activity. If you'd like your "island" represented, come join us at next month's meeting, Friday, 21 November via Zoom. Contact me for the details, or join us on the TPRF slack in the #community-roundtable channel. In this month's meeting, we had an update from the Board about fundraising and TPRC 2026 planning, some notes from the Mongueurs de Perl about PTS 2026. A call for more participation for FOSDEM 2026 in the event that a devroom for Perl/Raku is awarded. Salve also brought news about CPANSec activities, and Bruce rounded up some news from the Perl & Raku communities. [link] [comments] |
I have released Test2::Plugin::SubtestFilter for Perl tests, which allows filtering test targets by subtest name, similar to --testNamePattern
in jest and vitest. I think it is useful when you want to run only specific tests.
# t/test.t
use Test2::V0;
use Test2::Plugin::SubtestFilter;
subtest 'foo' => sub {
subtest 'nested foo1' => sub { ok 1 };
subtest 'nested foo2' => sub { ok 1 };
};
subtest 'baz' => sub {
ok 1;
};
done_testing;
# Run tests matching `foo1`
❯ SUBTEST_FILTER=foo1 prove -lvr t/test.t
t/test.t ..
# Seeded srand with seed '20251020' from local date.
ok 1 - foo {
ok 1 - nested foo1 {
ok 1
1..1
}
ok 2 - nested foo2 # skip
1..2
}
ok 2 - baz # skip
1..2
ok
All tests successful.
Files=1, Tests=2, 0 wallclock secs ( 0.00 usr 0.00 sys + 0.04 cusr 0.00 csys = 0.04 CPU)
Result: PASS
AHOCORASICK - Reduce heap allocation overhead A `U8*` structure is used to track character positions during Aho-Corasick string searching. This used to always be allocated from the heap, and wrapped in a mortal SV to avoid leakage. However, that incurs overhead. Following this commit: * A stack buffer is used if `maxlen` is small enough. * Otherwise, the heap allocation is saved directly to the savestack for freeing during stack unwinding. * Since a mortal SV is no longer used, there is no need to `SAVETMPS` and `FREETMPS` at scope entry/exit.
Binary Golf Grand Prix is an annual small file format competition, currently in it's sixth year. The goal is to make the smallest possible file that fits the criteria of the challenge.
This year's BGGP challenge was to output or display 6
. I always wanted to work with actual machine code, so I decided to submit a DOS COM
executable. Why? Because the COM
format has no headers or other metadata; you can just put some x86 instructions in a file and run it directly.
Having no experience with DOS, I started by looking up a "hello world" example and found https://github.com/susam/hello:
MOV AH, 9
MOV DX, 108
INT 21
RET
DB 'hello, world', D, A, '$'
This loads 9
into the AH
register (the upper byte of AX
) and executes interrupt 0x21, which triggers the DOS "display string" routine. The address of the string is given directly in DX
; $
is used as an in-band string terminator because DOS is weird.
Adapting this snippet to output 6
instead is trivial, but I discovered something better: Function 2 of interrupt 0x21 outputs a character (code given in DL
) directly. That gives us:
MOV AH, 2
MOV DL, '6'
INT 21
RET
Or in binary:
b4 02 b2 36 cd 21 c3
If you write these 7 bytes to a .COM
file, the result is already a fully functional DOS executable. And since the RET
command terminates the program, we can append whatever bytes we want, for example to create a palindrome:
b4 02 b2 36 cd 21 c3 21 cd 36 b2 02 b4
However, I've always liked polyglots. It turns out that the byte sequence 23 de
corresponds to the x86 instruction AND BX, SI
(which modifies BX
, but is harmless otherwise). And byte 23
happens to be character #
in ASCII, which means anything that follows will be ignored as a comment when the binary file is read by an interpreter that understands #
as a comment marker (which includes Perl, Python, Ruby, PHP, make, and the shell). This leads to the following x86/Perl polyglot:
#<DE><B4><02><B2>6<CD>!<C3>
print 6;
And with a few modifications, we get a palindrome again:
#<DE><B4><02><B2>6<CD>!<C3>#
print 6#6 tnirp
#<C3>!<CD>6<B2><02><B4><DE>#
This is also a valid shell script, but it tries to run a program called print
with arguments '6#6'
and 'tnirp'
. We can make the shell recognize #
as a comment marker by putting a space in front, but there is no print
command, so how do we make the shell use echo
while retaining print
for perl? Fortunately we don't need to if we're willing to use 6
as a "format string" and switch to printf
:
#<DE><B4><02><B2>6<CD>!<C3>#
printf 6 # 6 ftnirp
#<C3>!<CD>6<B2><02><B4><DE>#
We can do one better and add make
to the mix. We just need some form of dummy target and an empty list of prerequisites; the rest will be the shell command we already have. Normally that would look like this (with a literal tab before printf
):
foo:
printf 6
However, at least GNU make
lets you write it all in one line without using tabs:
foo: ; printf 6
This form happens to be valid Perl already: foo: ;
is just a label attached to a null statement. But the shell would try to run a program called foo:
and we don't want that. A creative choice of label name and spacing takes care of this problem as well:
true :;printf 6
To make
this means: The target true
can be created/updated (with no prerequisites) by running printf 6
. Since it is the first target in our "makefile", true
automatically becomes the default target.
To perl
this means: A label (named true
) is attached to a null statement, followed by printf(6)
(the final semicolon being optional because we're at the end of the file). 6
is implicitly converted to the format string "6"
, which simply outputs 6
.
To the shell this means: Run the true
command (with an argument of ':'
), then run the printf
command (with an argument of 6
).
In final 55-byte palindrome + x86 machine code form:
#<DE><B4><02><B2>6<CD>!<C3>#
true :;printf 6 # 6 ftnirp;: eurt
#<C3>!<CD>6<B2><02><B4><DE>#
That's it!
I can't get DBD-mysql to install on my Windows 11 machine.
Activestate Perl 5.40.2
DBD-mysql version 5.013 (Auto)
Mysql server 8.4
Mysql server is running.
Database 'test' has been created.
ContainerAdministrator with password 's3kr1t' has been granted all privileges for all databases.
state install dbd-mysql responds with:
• Searching for packages in the ActiveState Catalog ✔ Found
• Resolving Dependencies x Failed
x Could not plan build. Platform responded with:
Failed to create build plan due to solve errors
dbd-mysql is unavailable.
let.sources.solve
Because root depends on every version of Feature|language/perl|dbd-mysql which doesn't match any versions, version solving failed.
I have also tried doing the build on the Activestate site. I would post the results, but it is 502 lines of messages. But I did see this, which may be relevant:
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:LC_ALL = (unset), LC_CTYPE = (unset),
LC_NUMERIC = (unset),
LC_COLLATE = (unset),
LC_TIME = (unset),
LC_MONETARY = (unset),
LANG = "en_US.UTF-8"are supported and installed on your system.
perl: warning: Falling back to the system default locale ("English_United States.1252").
'mysql_config' is not recognized as an internal or external command, operable program or batch file.
Failed to determine directory of mysql.h
Hello.
% sw_vers ProductName:macOS ProductVersion:26.0.1 BuildVersion:25A362 % perl -v This is perl 5, version 40, subversion 2 (v5.40.2) built for darwin-thread-multi-2level
I'm trying to install Net::DHCP via CPAN, and Net::Pcap is a dependency. Net::Pcap's build succeeds, but the tests fail.
I've downloaded the Net::Pcap source, and get the exact same errors as when using CPAN. I've tried two different versions of libpcap, firstly the native macos version:
% perl Makefile.PL looking for -lpcap... yes checking for pcap_lib_version() in -lpcap... yes detecting available functions... ok detecting libpcap version... Running [cc -fno-common -DPERL_DARWIN -mmacosx-version-min=15.2 -DNO_THREAD_SAFE_QUERYLOCALE -DNO_POSIX_2008_LOCALE -fno-strict-aliasing -pipe -fstack-protector-strong -Wall -Wwrite-strings 'pcap_version.c' -lpcap -o pcap_version.exe] ok (libpcap version 1.10.1) Setting -DPERL_PCAP_VERSION=1010001 Checking if your kit is complete... Looks good Generating a Unix-style Makefile Writing Makefile for Net::Pcap Writing MYMETA.yml and MYMETA.json
and then the slightly newer version available via Homebrew:
% perl Makefile.PL INC=-I/opt/homebrew/opt/libpcap/include LIBS=-L/opt/homebrew/opt/libpcap/lib looking for -lpcap... yes checking for pcap_lib_version() in -lpcap... yes detecting available functions... ok detecting libpcap version... Running [cc -fno-common -DPERL_DARWIN -mmacosx-version-min=15.2 -DNO_THREAD_SAFE_QUERYLOCALE -DNO_POSIX_2008_LOCALE -fno-strict-aliasing -pipe -fstack-protector-strong -Wall -Wwrite-strings 'pcap_version.c' -L/opt/homebrew/opt/libpcap/lib -lpcap -o pcap_version.exe] ld: warning: building for macOS-15.2, but linking with dylib '/opt/homebrew/opt/libpcap/lib/libpcap.A.dylib' which was built for newer version 26.0 ok (libpcap version 1.10.5) Setting -DPERL_PCAP_VERSION=1010005 Checking if your kit is complete... Looks good Generating a Unix-style Makefile Writing Makefile for Net::Pcap Writing MYMETA.yml and MYMETA.json
In both cases the build succeeds (350 warnings generated, but no errors), but in both cases the tests fail with the exact same errors:
% sudo make test "/opt/homebrew/Cellar/perl/5.40.2/bin/perl" -MExtUtils::Command::MM -e 'cp_nonempty' -- Pcap.bs blib/arch/auto/Net/P> PERL_DL_NONLAZY=1 "/opt/homebrew/Cellar/perl/5.40.2/bin/perl" "-MExtUtils::Command::MM" "-MTest::Harness" "-e" "unde> # Testing Net::Pcap 0.21 (libpcap version 1.10.1) under Perl 5.040002 t/00-load.t ................ ok t/01-api.t ................. ok t/02-lookup.t .............. ok t/03-openlive.t ............ ok t/04-loop.t ................ ok t/05-dump.t ................ ok t/06-offline.t ............. ok t/07-stats.t ............... ok t/08-filter.t .............. ok t/09-error.t ............... ok t/10-fileno.t .............. ok t/11-snapshot.t ............ ok t/12-next.t ................ skipped: pcap_next() behaves too strangely for being tested on random machines t/13-dispatch.t ............ ok t/14-datalink.t ............ ok # This platform has been detected as a little endian architecture t/15-is_swapped.t .......... ok t/16-setnonblock.t ......... ok # libpcap version 1.10.1 t/17-lib_version.t ......... ok t/18-open_dead.t ........... ok t/19-breakloop.t ........... ok t/20-constants.t ........... ok t/21-next_ex.t ............. skipped: slowness and random failures... testing pcap_next_ex() is a PITA # Failed test 'pcap_open()' # at t/22-open.t line 90. # got: 'Your vendor has not defined pcap macro OPENFLAG_PROMISCUOUS, used at t/22-open.t line 89. # ' # expected: '' # Failed test ' - returned a defined value' # at t/22-open.t line 92. # Failed test '' - $pcap' isa 'SCALAR'' # at t/22-open.t line 93. # ' - $pcap' isn't defined # Failed test '' - $pcap' isa 'pcap_tPtr'' # at t/22-open.t line 94. # ' - $pcap' isn't defined # Failed test 'setbuff()' # at t/22-open.t line 98. # got: 'p is not of type pcap_tPtr at t/22-open.t line 97. # ' # expected: '' # Failed test ' - return 0 for true' # at t/22-open.t line 99. # got: undef # expected: '0' # Failed test 'setuserbuffer()' # at t/22-open.t line 103. # got: 'p is not of type pcap_tPtr at t/22-open.t line 102. # ' # expected: '' # Failed test ' - return 0 for true' # at t/22-open.t line 104. # got: undef # expected: '0' # Failed test 'setmode()' # at t/22-open.t line 108. # got: 'Your vendor has not defined pcap macro MODE_CAPT, used at t/22-open.t line 107. # ' # expected: '' # Failed test ' - return 0 for true' # at t/22-open.t line 109. # got: undef # expected: '0' # Failed test 'setmintocopy()' # at t/22-open.t line 113. # got: 'p is not of type pcap_tPtr at t/22-open.t line 112. # ' # expected: '' # Failed test ' - return 0 for true' # at t/22-open.t line 114. # got: undef # expected: '0' p is not of type pcap_tPtr at t/22-open.t line 116. # Looks like your test exited with 29 just after 24. t/22-open.t ................ Dubious, test returned 29 (wstat 7424, 0x1d00) Failed 12/24 subtests (less 11 skipped subtests: 1 okay) # Failed test 'createsrcstr() ' # at t/23-srcstr.t line 70. # got: 'Undefined subroutine &main::createsrcstr called at t/23-srcstr.t line 69. # ' # expected: '' # Failed test ' - should return zero: ' # at t/23-srcstr.t line 71. # got: undef # expected: '0' # Failed test ' - checking created source string' # at t/23-srcstr.t line 72. # got: '' # expected: 'rpcap://fangorn:12345/eth0' # Failed test 'parsesrcstr() ' # at t/23-srcstr.t line 76. # got: 'Undefined subroutine &main::parsesrcstr called at t/23-srcstr.t line 75. # ' # expected: '' # Failed test ' - should return zero: ' # at t/23-srcstr.t line 77. # got: undef # expected: '0' # Failed test ' - checking parsed type' # at t/23-srcstr.t line 78. # got: '' # expected: 'rpcap' # Failed test ' - checking parsed host' # at t/23-srcstr.t line 79. # got: '' # expected: 'fangorn' # Failed test ' - checking parsed port' # at t/23-srcstr.t line 80. # got: '' # expected: '12345' # Failed test ' - checking parsed name' # at t/23-srcstr.t line 81. # got: '' # expected: 'eth0' # Looks like you failed 9 tests of 18. t/23-srcstr.t .............. Dubious, test returned 9 (wstat 2304, 0x900) Failed 9/18 subtests (less 9 skipped subtests: 0 okay) t/24-offline_filter.t ...... ok t/50-anyevent-pcap.t ....... skipped: AnyEvent is not available t/50-net-pcap-easy.t ....... skipped: Net::Pcap::Easy is not available t/50-poe-component-pcap.t .. skipped: POE is not available t/distchk.t ................ skipped: Test::Distribution required for checking distribution t/pod.t .................... skipped: Test::Pod 1.14 required for testing POD t/podcover.t ............... skipped: Currently not working for Net::Pcap t/podspell.t ............... skipped: Pod spelling: for maintainer only t/portfs.t ................. skipped: Only for the module maintainer Test Summary Report ------------------- t/06-offline.t (Wstat: 0 Tests: 403 Failed: 0) TODO passed: 398 t/10-fileno.t (Wstat: 0 Tests: 21 Failed: 0) TODO passed: 19 t/22-open.t (Wstat: 7424 (exited 29) Tests: 24 Failed: 12) Failed tests: 12, 14-24 Non-zero exit status: 29 t/23-srcstr.t (Wstat: 2304 (exited 9) Tests: 18 Failed: 9) Failed tests: 10-18 Non-zero exit status: 9 Files=33, Tests=1557, 3 wallclock secs ( 0.10 usr 0.04 sys + 1.02 cusr 0.23 csys = 1.39 CPU) Result: FAIL Failed 2/33 test programs. 21/1557 subtests failed. make: *** [Makefile:1079: test_dynamic] Error 255
Any clues?
Thank you.
[link] [comments]
t/class/accessor.t - add additional :reader and :writer tests The :reader tests are not really needed at present, as `pp_leavesub` makes the necessary copies, but exploring some fast accessor ideas which bypass that logic has shown the usefulness of having these tests present. A test for :writer on an array field already exists in a separate file. A comment pointing to that file has been added, and a test for the hash case added next to the pre-existing test.
I know how to get all of the values of a hash given a set of keys:
use strict;
use warnings;
use DDP;
my %h = (
a => 1,
b => 2
);
my @v = @h{a,b};
p @v; # ( 1, 2 )
However I'm not sure how to get all of the values from a hash, excluding a key, without putting all of the keys, or using a for loop:
use strict;
use warnings;
use DDP;
my %h = (
a => 1,
b => 2,
c => 3,
d => 4,
e => 5
);
my @v;
for (keys %h) {
if ($_ eq 'd') {
next;
}
push @values, $h{$_};
}
p @v;
What I'd like to do is something like:
my @v = @h{!d};
Is there a more elegant way of doing this instead of just using a for-loop, and without knowing all of the keys before hand, just knowing the key we want to exclude?
Originally published at Perl Weekly 743
Hi there,
Last week I went to a small conference on "Teaching Computer Science in the era of AI/LLMs" hosted at The Academic College of Tel Aviv-Yaffo. It was very interesting to hear how at various institutions, for example at the Technion, at the Tel Aviv University and at Ben Gurion University the lecturers feel the need to change things as LLMs can now implement basically everything at the level of a CS student in a BSc program. How do you teach them to actually learn the syntax? How much should you let them use LLMs for the assignments and during the exams?
I personally teach a course at the Weizmann Institute of Science to Master's and Phd students of Biology and Life Sciences. I think 15 years ago there was a course in Perl, but now this is in Python. It is very different from teaching CS students. My students use the language a lot more to write one-time programs to analyze some data and most of them don't see themselves as 'programmers'. So they are way less interested in syntax, or 'good code'. They mostly want to get nice graphs.
It is quite clear that giving them well written assignments is rather pointless. They can just put the assignment in an LLM and get the result. So what can be done?
As the semester starts this coming Sunday I really need to figure out how and what to teach them. Should I basically teach them prompt engineering?
Related to this I'd love to hear from you - either in an email or better yet in a blog post - on how do you use LLMs to write code? Especially Perl code. Which LLMs do you use? How do you make your prompts better? Anything special to Perl code?
What would you recommend to fellow Perl developers, how to best use LLMs while writing Perl code?
I know we are all still trying to figure this out, but learning from each other could make this process easier.
Enjoy your week!
--
Your editor: Gabor Szabo.
Articles
The joy of rediscovering Perl
Wait a minute, Mr POSTman
Debugging POST request headers in under 40 screen rows. Doesn't actually use Postman.
Clipadd Cookbook
clipadd, a CLI included in the App::ClipboardUtils distribution, lets you add items to the desktop clipboard. It is clipboard-manager-agnostic, supporting backends like xclip and Klipper. It also has some convenient features which will be demonstrated in this document.
Porting from Perl to Go: Simplifying for Platform Engineering
Rewriting a script for the Homebrew package manager taught me how Go’s design choices align with platform-ready tools.
Discussion
Full stack javascript developer looking to find a perl job
Compute the Reverse Polish Notation using Perl
Would you like to add comments to this post with your suggestions on how to improve the code?
Managing memory in the GPU with Perl
perl XS modules for CUBIN files patching
Would love a good web hosting company to run Perl in a fcgi or plack or whatever
There are a number of companies where one can have a full VPS for 4-5 USD / month with root access. You can run your own code there directly on the VPS or you can run a Docker container on it and as the load on the system growth you can upgrade the VPS to something bigger for slightly more money. I have been using Linode and Digital Ocean for many years and I am very satisfied.
Perl
This week in PSC (204) | 2025-10-13
The Weekly Challenge
The Weekly Challenge by Mohammad Sajid Anwar will help you step out of your comfort-zone. You can even win prize money of $50 by participating in the weekly challenge. We pick one champion at the end of the month from among all of the contributors during the month, thanks to the sponsor Lance Wicks.
The Weekly Challenge - 344
Welcome to a new week with a couple of fun tasks "Array Form Compute" and "Array Formation". If you are new to the weekly challenge then why not join us and have fun every week. For more information, please read the FAQ.
RECAP - The Weekly Challenge - 343
Enjoy a quick recap of last week's contributions by Team PWC dealing with the "Zero Friend" and "Champion Team" tasks in Perl and Raku. You will find plenty of solutions to keep you busy.
TWC343
Clear, effective, and showcasing strong Perl programming abilities. While Task 2 efficiently counts wins in a match matrix with clear logic and sensible hash usage, Task 1 demonstrates succinct numeric handling with strong absolute value comparisons. All things considered, the answers are clear, accurate, and elegantly demonstrate how to solve real-world problems using Perl.
Team Zero
For both inexperienced and seasoned Raku developers, this is a great resource. It explores the nuances of the language and offers insightful lessons in efficient coding techniques in addition to offering workable answers to typical issues.
Friendly Champions
These solutions demonstrate superior algorithmic thinking and Perl programming at the expert level. This submission is incredibly impressive because it combines mathematical insight, language proficiency, and innovative problem-solving.
Perl Weekly Challenge 343
The solutions demonstrate the useful application of PDL for array and matrix operations and are technically sound and reliable. They are great illustrations of algorithmic problem-solving in Perl since they are not only accurate but also optimized for readability and maintainability.
The Zero Champion
In addition to overcoming the obstacles, Matthias did so in a way that inspired and educated others. Programming challenges are valuable because of contributions like this one.
Ze-ro the CHAMPIONS!
In addition to accurately resolving the difficulties, Packy has produced a superb analysis of how to apply the same algorithm across various programming paradigms while honoring the idioms and advantages of each language.
No friends among the champions
Both solutions demonstrate a thorough grasp of Perl's capabilities and are executed with clarity and efficiency. They make sure the code is both functional and maintainable by following best practices for coding.
The Weekly Challenge #343
Robbie's solutions show a thorough comprehension of the problem requirements and the ability to apply efficient algorithms. They are both elegant and mathematically sound. His creative method of breaking the tie in Task 2 gives the solution an additional level of complexity.
Zero to Champion
Roger's solutions exhibit a thorough comprehension of the problem requirements and are well-structured. The code is efficient and simple to understand because it makes use of Perl's built-in functions and simple logic. These implementations are great illustrations of how to precisely and clearly approach algorithmic problems.
It’s hard to make friends when you’re a zero
The two challenges are thoughtfully and technically insightfully explored in Ryan's post. His elegant and effective solutions demonstrate a thorough comprehension of Perl's capabilities.
Absolute Champion
Simon's solution shows a thorough grasp of Python's capabilities and is both simple and effective. He makes sure the solution is reliable and capable of handling a variety of numerical inputs by utilizing the decimal module.
Weekly collections
NICEPERL's lists
Great CPAN modules released last week;
MetaCPAN weekly report.
Events
Paris.pm monthly meeting
November 12, 2025
Toronto.pm - online - How SUSE is using Perl
December 6, 2025
Paris.pm monthly meeting
December 10, 2025
You joined the Perl Weekly to get weekly e-mails about the Perl programming language and related topics.
Want to see more? See the archives of all the issues.
Not yet subscribed to the newsletter? Join us free of charge!
(C) Copyright Gabor Szabo
The articles are copyright the respective authors.
Show that you can't goto into optimized-away construct Incorporating suggestions from Lukas Mai. Use less generic names for labels in test files. Fixes: GH #23810
toke.c: Fix inconsistency under 'use utf8' This code can't work properly: if (UTF ? isIDFIRST_utf8((U8*)s+1) : isWORDCHAR_A(s[1])) Suppose you have a string composed entirely of ASCII characters beginning with a digit. If the string isn't encoded in UTF-8, the condition is true, but it is false if the string happens to have the UTF-8 flag set for whatever reason. One of those reasons simply is that the Perl program is being compiled under 'use utf8'. The UTF-8 flag should not change the behavior of ASCII strings. The code was introduced in 9d58dbc453a86c9cbb3a131adcd1559fe0445a08 in 2015, to fix [perl #123963] "@<fullwidth digit>". The line it replaced was if (isWORDCHAR_lazy_if(s+1,UTF)) (The code was modified in 2016 by fac0f7a38edc4e50a7250b738699165079b852d8 as part of a global substitution to use isIDFIRST_utf8_safe() so as to have no possibility of going off the end of the buffer), but that did not affect the logic. The problem the original commit was trying to solve was that fullwidth digits (U+FF10 etc) were accepted when they shouldn't be, whereas [0-9] should remain as being accepted. The defect is that [0-9] stopped being accepted when the UTF-8 flag is on. The solution is to change it to instead be if (isDIGIT_A(s[1]) || isIDFIRST_lazy_if_safe(s+1, send, UTF)) This causes [0-9] to remain accepted regardless of the UTF-8 flag. So when it is on, the only difference between before this commit and after is that [0-9] are accepted. In the ASCII range, the only difference between \w and IDFirst is that the former includes the digits 0-9, so when the UTF-8 flag is off this evaluates to isWORD_CHAR_A, as before. (Changing to isIDFIRST from isWORDCHAR in the original commit did solve a bunch of other cases where a \w is not supposed to be the first character in a name. There are about 4K such characters currently in Unicode.)
Hello everyone, now this seems kinda of cringe ig, but here is the thing i am a full stack developer with javascript and although it is okay, i am getting tired of web dev, and i am looking to switch to more "advanced" software fields and i made a search on "software developer jobs no one wants" and i saw a comment about perl, so my inquiry is, if any of you are in need or looking for an intern(or if you know where i can find such jobs), i am willing to work for 1 month free and in the process also learn the language. Now this is a huge shot and even if the mods delete this then no biggie.
Thank you in advance
[link] [comments]
-
App::DBBrowser - Browse SQLite/MySQL/PostgreSQL databases and their tables interactively.
- Version: 2.435 on 2025-10-14, with 18 votes
- Previous CPAN version: 2.434 was 9 days before
- Author: KUERBIS
-
App::rdapper - a command-line RDAP client.
- Version: 1.20 on 2025-10-14, with 21 votes
- Previous CPAN version: 1.19 was 1 month, 13 days before
- Author: GBROWN
-
CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
- Version: 20251018.003 on 2025-10-19, with 25 votes
- Previous CPAN version: 20251017.001 was 1 day before
- Author: BRIANDFOY
-
Cucumber::TagExpressions - A library for parsing and evaluating cucumber tag expressions (filters)
- Version: 8.0.0 on 2025-10-14, with 16 votes
- Previous CPAN version: 6.2.0 was 4 months, 20 days before
- Author: CUKEBOT
-
Data::ObjectDriver - Simple, transparent data interface, with caching
- Version: 0.26 on 2025-10-17, with 16 votes
- Previous CPAN version: 0.25 was 6 months, 1 day before
- Author: SIXAPART
-
Finance::Quote - Get stock and mutual fund quotes from various exchanges
- Version: 1.67 on 2025-10-18, with 143 votes
- Previous CPAN version: 1.66_02 was 1 month, 17 days before
- Author: BPSCHUCK
-
Google::Ads::GoogleAds::Client - Google Ads API Client Library for Perl
- Version: v29.0.1 on 2025-10-16, with 20 votes
- Previous CPAN version: v29.0.0 was 1 day before
- Author: CHEVALIER
-
JSON::Schema::Modern - Validate data against a schema using a JSON Schema
- Version: 0.620 on 2025-10-15, with 12 votes
- Previous CPAN version: 0.619 was 17 days before
- Author: ETHER
-
SPVM - The SPVM Language
- Version: 0.990105 on 2025-10-16, with 36 votes
- Previous CPAN version: 0.990104 was 1 day before
- Author: KIMOTO
-
Term::Choose - Choose items from a list interactively.
- Version: 1.777 on 2025-10-15, with 15 votes
- Previous CPAN version: 1.776 was 18 days before
- Author: KUERBIS
-
Time::Piece - Object Oriented time objects
- Version: 1.38 on 2025-10-18, with 64 votes
- Previous CPAN version: 1.3701 was 1 month, 23 days before
- Author: ESAYM
-
Type::Tiny - tiny, yet Moo(se)-compatible type constraint
- Version: 2.008004 on 2025-10-17, with 145 votes
- Previous CPAN version: 2.008003 was 1 month, 15 days before
- Author: TOBYINK
This is the weekly favourites list of CPAN distributions. Votes count: 105
Week's winner: DBD::DuckDB (+3)
Build date: 2025/10/19 06:44:20 GMT
Clicked for first time:
- App::MHFS - A Media HTTP File Server. Stream your own music and video library via your browser and standard media players.
- Crypt::Credentials - Manage credential files
- mojo::debugbar - Debugbar for Mojolicious
- Mojo::TypeModel - A very simple model system using Mojo::Base
- Mojolicious::Plugin::Airbrake -
- Mojolicious::Plugin::Credentials - A credentials store in mojo
- Mojolicious::Plugin::DomIdHelper - Mojolicious plugin to generate DOM IDs and CSS class names from your ORM objects
- Mojolicious::Plugin::ExceptionSentry - Sentry Plugin for Mojolicious - Mojolicious::Plugin::ExceptionSentry
- Mojolicious::Plugin::Gallery - Simple photo gallery for Mojolicious
- Mojolicious::Plugin::JavaScript::Console - use the JavaScript console from Mojolicious applications
- Mojolicious::Plugin::Mailgun - Easy Email sending with mailgun
- Mojolicious::Plugin::SendEmail - Easily send emails from Mojolicious applications
- Mojolicious::Plugin::Statsd - Emit to Statsd, easy!
- Mojolicious::Plugin::StrictCORS - Strict CORS routes in your Mojolicious app
- Mojolicious::Plugin::Thumbnail - Use Imager in Mojolicious
- Mojolicious::Plugin::Wordpress - Use Wordpress as a headless CMS
- Params::Validate::Strict - Validates a set of parameters against a schema
Increasing its reputation:
- App::Cmd (+2=51)
- App::cpm (+2=76)
- App::DBBrowser (+1=18)
- App::Netdisco (+1=18)
- Class::Autouse (+1=3)
- Class::Data::Inheritable (+1=6)
- Convert::Color (+1=11)
- CPANSA::DB (+2=4)
- Crypt::Eksblowfish (+1=17)
- Crypt::URandom (+1=6)
- Crypt::URandom::Token (+1=3)
- CSS::Sass (+1=10)
- DateTime::Format::Natural (+1=19)
- DBD::DuckDB (+3=3)
- DBD::MariaDB (+1=10)
- DBD::mysql (+1=62)
- Dist::Zilla (+1=187)
- Emacs::EPL (+1=2)
- Email::MIME (+1=22)
- Email::Sender (+1=52)
- Email::Stuffer (+1=37)
- Geo::IP (+1=14)
- Hash::Ordered (+1=35)
- HTTP::Message (+1=70)
- Image::Magick (+1=4)
- IO::Socket::IP (+1=22)
- JOAP (+1=2)
- libwww::perl (+1=172)
- Lingua::EN::PluralToSingular (+1=3)
- Math::Symbolic (+1=6)
- Mercury (+1=10)
- MIME::Lite (+1=35)
- MIME::tools (+1=15)
- Minion::Notifier (+1=10)
- Mojo::AsyncAwait (+1=10)
- Mojo::Redis (+1=20)
- Mojo::Sendgrid (+1=2)
- Mojo::Weixin (+1=11)
- Mojolicious (+1=508)
- Mojolicious::Plugin::AccessLog (+1=5)
- Mojolicious::Plugin::AssetPack (+1=51)
- Mojolicious::Plugin::Authentication (+1=18)
- Mojolicious::Plugin::AutoReload (+1=5)
- Mojolicious::Plugin::CSRFProtect (+1=8)
- Mojolicious::Plugin::DateTime (+1=2)
- Mojolicious::Plugin::DBViewer (+1=9)
- Mojolicious::Plugin::DOCRenderer (+1=2)
- Mojolicious::Plugin::EventSource (+1=4)
- Mojolicious::Plugin::Mail (+1=14)
- Mojolicious::Plugin::MountPSGI (+1=3)
- Mojolicious::Plugin::Notifications (+1=7)
- Mojolicious::Plugin::NYTProf (+1=19)
- Mojolicious::Plugin::OpenAPI (+1=47)
- Mojolicious::Plugin::PPI (+1=6)
- Mojolicious::Plugin::Prove (+1=6)
- Mojolicious::Plugin::SecureCORS (+1=6)
- Mojolicious::Plugin::Sentry (+1=2)
- Mojolicious::Plugin::Status (+1=18)
- Mojolicious::Plugin::SwaggerUI (+1=8)
- Mojolicious::Plugin::Web::Auth (+1=16)
- Moo (+1=307)
- Moose (+1=334)
- Net::HTTP (+1=12)
- Object::Result (+1=4)
- Object::Simple (+1=12)
- PAR (+1=20)
- Path::AttrRouter (+1=2)
- Path::Class (+1=83)
- PDK::DBI (+1=2)
- Plack::Middleware::ETag (+1=3)
- Plack::Middleware::Statsd (+1=4)
- PPI (+1=64)
- PPI::HTML (+1=3)
- Redis (+1=43)
- Sentry::Raven (+1=4)
- Sys::SigAction (+1=13)
- Text::Treesitter (+1=5)
- Time::Moment (+1=76)
- Toadfarm (+1=22)
- Type::Tiny (+1=145)
- UV (+1=14)
- WebService::Ollama (+1=3)
- WWW::Mechanize (+1=102)
use feature "switch";
# Construct operator priority data
%priority_of_operation=(
'+'=>1,
'-'=>1,
'*'=>2,
'/'=>2,
'('=>0,
')'=>0
);
# Expression to be calculated
$prepare_exec="5.98+((1+2211.12)*4)-3";
$prepare_endexpres=$prepare_exec;
# Convert to Reverse Polish Notation
# Create operator stack for storing operators
@operationstack=qw();
$exeexpress=$prepare_endexpres;
$result="";
print "String to scan: $exeexpress\n";
while (length($exeexpress)>0)
{
# Scan expression sequentially, if current character is a number, output it directly
if ($exeexpress =~ /^(\d+)/ | $exeexpress =~/^((\d+)\.(\d*))/){
print "Number: $1\n";
$result.=($1." ");
print "Expression: $result\n";
$exeexpress=substr($exeexpress,length($1),length($exeexpress)-length($1));
print "String to scan: $exeexpress\n";
}
# If current operator is '(', push directly to stack
elsif($exeexpress =~ /^\(+?/){
print "Operator: (\n";
print "Operator ( pushed to stack\n";
push @operationstack,"(";
print "Current operator stack: @operationstack\n";
print "Expression: $result\n";
$exeexpress=substr($exeexpress,1,length($exeexpress)-1);
print "String to scan: $exeexpress\n";
}
# If it's ')', pop and output operators sequentially until encountering first '(', first '(' is popped but not output
elsif($exeexpress =~ /^\)+?/){
print "Operator: )\n";
$temp=pop @operationstack;
while ($temp ne "(") {
$result.=($temp." ");
print "Operator $temp popped, expression: $result\n";
$temp=pop @operationstack;
print "Current operator stack: @operationstack\n";
}
$exeexpress=substr($exeexpress,1,length($exeexpress)-1);
print "String to scan: $exeexpress\n";
}
# If it's arithmetic operator, compare priority of top stack element with current element. If priority of top stack element operator >= priority of current element, pop and output operators sequentially until priority of top stack element < priority of current element, then push current element to stack;
elsif($exeexpress =~/^(\+|\-|\*|\/)?(.*)$/)
{
print "Operator: $1\n";
print "Current operator stack: @operationstack\n";
$curoperation=$1 ;
$topoperation=pop @operationstack;
push @operationstack,$topoperation;
if ($topoperation){
if ($priority_of_operation{$curoperation}>$priority_of_operation{$topoperation})
{
# If this character's priority is higher than the operator at top of operator stack, push this operator to stack
push @operationstack,$curoperation;
print "Operator $curoperation pushed to stack\n";
$exeexpress=substr($exeexpress,1,length($exeexpress)-1);
print "String to scan: $exeexpress\n";
}
else{
# If priority of top stack element operator >= priority of current element, pop and output operators sequentially until priority of top stack element < priority of current element, then push current element to stack;
while ($priority_of_operation{$curoperation}<=$priority_of_operation{$topoperation})
{
$topoperation=pop @operationstack;
if (not $topoperation) {
last;
}
$result.=($topoperation." ");
print "Operator $topoperation popped, expression: $result\n";
}
push @operationstack,$curoperation;
$exeexpress=substr($exeexpress,1,length($exeexpress)-1);
print "String to scan: $exeexpress\n";
}
}
else{
push @operationstack,$curoperation;
print "Expression: $result\n";
$exeexpress=substr($exeexpress,1,length($exeexpress)-1);
print "String to scan: $exeexpress\n";
}
print "Current operator stack: @operationstack\n";
}
}
while ((scalar @operationstack)>0){
$result.=pop @operationstack;
}
print "Expression: $result\n";
$mycode=$result;
@waitingexe=split (" ",$mycode);
$mylength= scalar @waitingexe;
print "Reverse Polish Notation to calculate: $mycode\n";
print "Starting Reverse Polish Notation calculation\n";
@exestack=qw();
# Output current stack contents
print "Current stack contents: @exestack\n";
for ($i=0;$i<$mylength;$i++){
# Start Reverse Polish Notation calculation
$myvar=$waitingexe[$i];
print "Element read: $myvar =>";
given ($myvar){
when ("+") { $num2=pop @exestack;$num1=pop @exestack; $result=$num1+$num2;push @exestack,$result;print "$num1+$num2=$result, $num2 and $num1 popped, $result pushed\n";}
when ("-") { $num2=pop @exestack;$num1=pop @exestack; $result=$num1-$num2;push @exestack,$result;print "$num1-$num2=$result, $num2 and $num1 popped, $result pushed\n";}
when ("*") { $num2=pop @exestack;$num1=pop @exestack; $result=$num1*$num2;push @exestack,$result;print "$num1*$num2=$result, $num2 and $num1 popped, $result pushed\n";}
when ("/") { $num2=pop @exestack;$num1=pop @exestack; $result=$num1/$num2;push @exestack,$result;print "$num1/$num2=$result, $num2 and $num1 popped, $result pushed\n";}
# Numbers pushed directly to stack
default {push @exestack,$myvar;print "$myvar pushed to stack\n";}
}
# Output current stack contents
print "Current stack contents: @exestack=>";
}
$endresult=pop @exestack;
print "Result: $endresult\n";
sub trim
{
my $string = shift;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return $string;
}
The above code evaluates a mathematical expression.
We will use the following example to illustrate the computation process.
The infix expression "5 + ((1 + 2) * 4) - 3" is written in Reverse Polish Notation as:
5 1 2 + 4 * + 3 -
The table below illustrates the step-by-step evaluation of this RPN expression from left to right.
Input | Action | Stack | Comment |
---|---|---|---|
5 | Push | 5 | |
1 | Push | 5, 1 | |
2 | Push | 5, 1, 2 | |
+ | Addition | 5, 3 | Pop (1, 2); push result (3) |
4 | Push | 5, 3, 4 | |
* | Multiplication | 5, 12 | Pop (3, 4); push result (12) |
+ | Addition | 17 | Pop (5, 12); push result (17) |
3 | Push | 17, 3 | |
- | Subtraction | 14 | Pop (17, 3); push result (14) |
When the calculation is complete, only one operand remains in the stack, which is the final result of the expression: 14.
Wrote an infrastructural module to control memory traffic and manage memory on the GPU using Perl and OpenMP.
https://github.com/chrisarg/Task-MemManager-Device
Examples show how one can get memory in and out of the device and update memory regions (e.g. PDL ndarrays as shown in one of the examples).
I decided to split this functionality out of my bitset/bitvector library Bit (https://github.com/chrisarg/Bit) and it's Perl interface Bit::Set (https://github.com/chrisarg/Bit-Set) when I realized that the stuff I was doing to accelerate these codes could be of general use.
The module is part of the https://github.com/chrisarg/Task-MemManager ecosystem which (hopefully) will allow tighter integration of Perl with low level languages especially assembly for systems programming.
[link] [comments]
Rewriting a script for the Homebrew package manager taught me how the Go programming language’s design choices align with platform-ready…
When using the perl library Storable to store and retrieve XML::LibXML document objects, I get a segmentation fault. Specifically, once I use the LibXML routine findnodes
. My question is: why, and is there a way around it?
For example consider the following code:
use strict;
use warnings;
use XML::LibXML;
use Storable;
use Devel::Size qw/size/;
use Data::Dumper qw/Dumper/;
my $file = "test.xml";
my $store_file = "./dom.xml.store";
my $dom;
if(-e $store_file ){
$dom = retrieve $store_file;
print "dom retrieved from storable $store_file\n";
}else{
$dom = XML::LibXML->load_xml(location => $file);
print "dom retrieved from xml $file\n";
store $dom, $store_file;
}
print Dumper $dom;
print "Dom ref(". ref($dom) . "), size(" . size($dom) . ")\n";
#test
foreach my $title ($dom->findnodes('//title')){
print $title->to_literal() . "\n";
}
Running this code twice yields the following result:
$~/perl:perl libxml.pl
dom retrieved from xml test.xml
$VAR1 = bless( do{\(my $o = '93874673292592')}, 'XML::LibXML::Document' );
Dom ref(XML::LibXML::Document), size(72)
Apollo 13
Solaris
Ender's Game
Interstellar
The Martian
$~/perl:perl libxml.pl
dom retrieved from storable ./dom.xml.store
$VAR1 = bless( do{\(my $o = '93874673292592')}, 'XML::LibXML::Document' );
Dom ref(XML::LibXML::Document), size(72)
Segmentation fault
$~/perl:
The xml file which I named test.xml file is the same file retrieved from the tutorial here. Similar tests were run on my system with unblessed and blessed perl objects, neither of which caused the segmentation fault.
Usual disclaimers: perl version: v5.26.3 Storable version 3.11 LibXML version 2.0132
I have a minimal working example of two nested while
loops on the same array @g
:
#!/usr/bin/env perl
use 5.042;
no source::encoding;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':default';
my @g = qw(Matthew Mark Luke John);
while (my ($i, $gos) = each @g) {
say "$i => $gos";
while (my ($j, $g2) = each @g) {
say "\t$j=>$g2";
}
}
This particular loop gets stuck on the first iteration $i
, and never moves on to the 2nd.
The infinity is easily fixed by foreach my $gos (@gos) {
instead of the first while
.
Is this a bug or an intended feature?
Shouldn't foreach my $gos (@g)
in the first line be the exact equivalent of while (my ($i, $gos) = each @g) {
?
Debugging POST request headers in under 40 screen rows. Doesn't actually use Postman
While developing a Perl client for the Harvard Astrophysics Data System API, I was getting errors from my POST requests. Not usually a problem to fix, but I connect to the host machine via a dodgy terminal session that hangs up when the screensaver kicks in or when the wind blows from the East. To keep from having to restart the 6+ windows that I have open in my dev env every time I go for a walk, I start a tmux session running on the host which ignores the SIGHUP and is just where I left it when I reconnect, no .swp files to clean up.
The problem is that I can't scroll up in tmux like I do in a regular terminal1 which leaves me with 40 rows to read the error returned from the POST request. This is nowhere near enough. Hmmm...
💡 Remember that you're using LWP::UserAgent::Mockable (or its Mojo cousin) to record the tests to avoid using the network during the test pipeline. Realise that all the traffic from those network calls are stored as plain text files and you don't have to mess around with tcpdump just to inspect the HTTP headers anymore.
The raw file itself is a bit messy to look at (maybe I should write a quick tool that deserializes it for STDOUT), but it shows me that the Authorization header just isn't there. But it's in my code, I made sure. See right after the call to post ...
Ahh, after some reflection I realize that the post
method makes the HTTP request as soon as it's invoked, which is why I was using build_tx in the GET request to add my Dev Key, like so
my $tx = $self->ua->build_tx( GET => $url );
$tx->req->headers->authorization( 'Bearer ' . $self->token );
...
try { $tx = $self->ua->start($tx) }
catch ($error) { ...
}
Now, the response has changed from UNAUTHORIZED to INTERNAL SERVER ERROR. I try the curl
command suggested by the docs and that works fine. Look back in the mock file and see ... no payload.
Quietly add the json
attribute to the transaction constructor
my $tx = $self->ua->build_tx( POST => $url, json => $hash );
because sending a JSON payload is so damn easy in Mojo.
All done - and Robert is your mother's brother.
-
why yes, you can scroll up and down in a tmux session when you remember to enter Copy mode with
Prefix [
so you get the arrow keys and the Page Up/Down buttons to play with.Enter
to exit. ↩
(A version of this article also appears as App::ClipboardUtils::Manual::HowTo::ClipaddCookbook on CPAN.)
Introduction
clipadd, a CLI included in the App::ClipboardUtils distribution, lets you add items to the desktop clipboard. It is clipboard-manager-agnostic, supporting backends like xclip and Klipper. It also has some convenient features which will be demonstrated in this document.
Adding multiple entries from a text file
% clipadd --split-by '\n---\n' < FILENAME
For example, after adding FAQ entries from this text file:
Question 1 Answer 1 --- Question 2 Answer 2 --- Question 3 Answer 3
then you can setup keyboard shortcuts like Ctrl-Alt-Up and Ctrl-Alt-Down in Klipper to quickly access those FAQ entries to provide quick answers in chats.
Adding ouput from command-line, one line at a time
% clipadd -c COMMAND % clipadd -c COMMAND -t ; # to see the output on stdout as well
This is convenient because it lets you feed input to a command interactively and immediately get the output as clipboard content.
For example, using with safer:
% clipadd -c safer -t Foo Bar, Inc. foo-bar-inc _
I can paste company names and get a slug name to use in file manager to create directory, etc.
Adding text fragments from a text file as clipboard contents
% clipadd --fragments < FILENAME
This is a variation of the previous recipe. Using fragments allows you to only add certain entries and in custom order. Example content of text file:
FAQS ==== Question 1 # BEGIN clipadd id=2 Answer 1 # END clipadd id=2 Question 2 # BEGIN clipadd id=1 Answer 2 # END clipadd id=1 Question 3 # BEGIN clipadd id=3 Answer 3 # END clipadd id=3 Question 4 Answer 4
See Also
clipadd (a.k.a. add-clipboard-content, a.k.a. ca).
Rewriting a script for the Homebrew package manager taught me how Go’s design choices align with platform-ready tools.
The problem with brew upgrade
By default, the brew upgrade
command updates every formula (terminal utility or library). It also updates every cask (GUI application) it manages. All are upgraded to the latest version — major, minor, and patch. That’s convenient when you want the newest features, but disruptive when you only want quiet patch-level fixes.
Last week I solved this in Perl with brew-patch-upgrade.pl
, a script that parsed brew upgrade
’s JSON output, compared semantic versions, and upgraded only when the patch number changed. It worked, but it also reminded me how much Perl leans on implicit structures and runtime flexibility.
This week I ported the script to Go, the lingua franca of DevOps. The goal wasn’t feature parity — it was to see how Go’s design choices map onto platform engineering concerns.
Why port to Go?
- Portfolio practice : I’m building a body of work that demonstrates platform engineering skills.
- Operational focus : Go is widely used for tooling in infrastructure and cloud environments.
- Learning by contrast : Rewriting a working Perl script in Go forces me to confront differences in error handling, type safety, and distribution.
The journey
Error handling philosophy
Perl gave me try
/catch
(experimental in the Perl v5.34.1 that ships with macOS, but since accepted into the language in v5.40). Go, famously, does not. Instead, every function returns an error explicitly.
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;
}
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 version is noisier, but it forces explicit decisions. That’s a feature in production tooling: no silent failures.
Dependency management
-
Perl :
cpanfile
+ CPAN modules. Distribution means “install Perl (if it’s not already), install modules, run script.” Tools likecarton
and thecpan
orcpanm
commands help automate this. Additionally, one can use further tooling likefatpack
andpp
to build more self-contained packages. But those are neither common nor (except forcpan
) distributed with Perl. -
Go :
go.mod
+go build
. Distribution is a single (platform-specific) binary.
For operational tools, that’s a massive simplification. No runtime interpreter, no dependency 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 compiler enforces assumptions that Perl left implicit. That friction is valuable — it surfaces errors early.
Binary distribution
This is where Go shines. Instead of telling colleagues “install Perl v5.34 and CPAN modules,” I can hand them a binary. No need to worry about scripting runtime environments — just grab the right file for your system.
-
homebrew-semver-guard-darwin
(Universal Binary for macOS) -
homebrew-semver-guard-linux-amd64
(Intel/AMD 64-bit binary for Linux) -
homebrew-semver-guard-linux-arm64
(Arm 64-bit binary for Linux)
Available on the release page. Download, run, done.
Semantic versioning logic
In Perl, I manually compared arrays of version numbers. In Go, I imported 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 legible, and less error-prone. The library encodes the convention, so I don’t have to.
Deliberate simplification
I didn’t port every feature. Logging adapters, signal handlers, and edge-case diagnostics remained in Perl. The Go version focuses on the core logic: parse JSON, compare versions, run upgrades. That restraint was intentional — I wanted to learn Go’s idioms, not replicate every Perl flourish.
Platform engineering insights
Three lessons stood out:
- Binary distribution matters. Operational tools should be installable with a single copy step. Go makes that trivial.
- Semantic versioning is an operational practice. It’s not just a convention for library authors — it’s a contract that tooling can enforce.
- Go’s design aligns with platform needs. Explicit errors, type safety, and static binaries all reduce surprises in production.
Bringing it home
This isn’t a “Perl vs. Go” story. It’s a story about deliberate simplification, taking a working Perl script and recasting it in Go. The aim is to see how the language’s choices shape a solution to the same problem.
The result is homebrew-semver-guard
v0.1.0, a small but sturdy tool. It’s not feature-finished, but it’s production-ready in the ways that matter.
Next up: I’m considering more Go tools, maybe even Kubernetes for services on my home server. This port was a practice, an artifact demonstrating platform engineering in action.
Links
- Original Perl script:
brew-patch-upgrade.pl
- Go release:
homebrew-semver-guard
v0.1.0 - Last week’s post: Patch-Perfect: Smarter Homebrew Upgrades on macOS
All three of us attended.
- We touched on the recent discussion about the classification of modules included with the perl distribution. We agreed that the PSC and p5p need to do a better job of tracking which modules have active maintainers and who they are. Something like a dashboard would be useful for this, and we identified as a starting point that it would be good to have an up-to-date overview of dists, their maintainers, and whether they are active. Maintainers.pl probably already covers the list of dists but it would be useful to split the data out into a separate file so it could be used in other scripts, which could also populate records with additional data, outside of what Maintainers.pl needs, for their own purposes. For example a tool for querying PAUSE for the maintainers of dual-life dists and flagging deviations could check a custom field where known deviations (along with a comment) would be recorded, so as to not flag them.
- We had a somewhat surface-level discussion about the future of stdio in Perl. Leon has been thinking about this and intends to write a separate mail to p5p about it.
How can I substitute every character that is:
not WORD (a-z, A-Z, 0-9) and also
not minus (-) and also
not hash (#)
to underline (_)
e.g.
"aB-09 !#" => "aB-09__#"
"=a_-#(0" => "_a_-#_0"
I started with:
s/\W/_/g
for the not-word, but I'm not sure about the not - and not #.
-
App::Cmd - write command line apps with less suffering
- Version: 0.338 on 2025-10-03, with 49 votes
- Previous CPAN version: 0.337 was 9 months, 3 days before
- Author: RJBS
-
App::DBBrowser - Browse SQLite/MySQL/PostgreSQL databases and their tables interactively.
- Version: 2.434 on 2025-10-05, with 17 votes
- Previous CPAN version: 2.433 was 6 days before
- Author: KUERBIS
-
App::MHFS - A Media HTTP File Server. Stream your own music and video library via your browser and standard media players.
- Version: v0.7.0 on 2025-10-07, with 14 votes
- Previous CPAN version: v0.6.0 was 11 months, 16 days before
- Author: GAHAYES
-
App::Netdisco - An open source web-based network management tool.
- Version: 2.091001 on 2025-10-09, with 762 votes
- Previous CPAN version: 2.091000 was 8 days before
- Author: OLIVER
-
App::Sqitch - Sensible database change management
- Version: v1.6.0 on 2025-10-06, with 3041 votes
- Previous CPAN version: v1.5.2 was 5 months, 6 days before
- Author: DWHEELER
-
CGI - Handle Common Gateway Interface requests and responses
- Version: 4.71 on 2025-10-01, with 46 votes
- Previous CPAN version: 4.70 was 2 months, 24 days before
- Author: LEEJO
-
CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
- Version: 20251003.001 on 2025-10-03, with 25 votes
- Previous CPAN version: 20250807.001 was 1 month, 27 days before
- Author: BRIANDFOY
-
DateTime::Format::Natural - Parse informal natural language date/time strings
- Version: 1.22 on 2025-10-01, with 18 votes
- Previous CPAN version: 1.21_01 was 1 month, 12 days before
- Author: SCHUBIGER
-
Imager - Perl extension for Generating 24 bit Images
- Version: 1.029 on 2025-10-06, with 68 votes
- Previous CPAN version: 1.028 was 3 months, 19 days before
- Author: TONYC
-
Mojolicious - Real-time web framework
- Version: 9.42 on 2025-10-01, with 507 votes
- Previous CPAN version: 9.41 was 2 months, 29 days before
- Author: SRI
-
Selenium::Remote::Driver - Perl Client for Selenium Remote Driver
- Version: 1.50 on 2025-10-06, with 49 votes
- Previous CPAN version: 1.49 was 2 years, 6 months before
- Author: TEODESIAN
-
Specio - Type constraints and coercions for Perl
- Version: 0.53 on 2025-10-04, with 12 votes
- Previous CPAN version: 0.52 was 1 month, 26 days before
- Author: DROLSKY
-
SPVM - The SPVM Language
- Version: 0.990102 on 2025-10-10, with 36 votes
- Previous CPAN version: 0.990101 was before
- Author: KIMOTO
-
WWW::Mechanize::Chrome - automate the Chrome browser
- Version: 0.74 on 2025-10-06, with 22 votes
- Previous CPAN version: 0.73 was 1 year, 6 months, 7 days before
- Author: CORION
-
YAML::Syck - Fast, lightweight YAML loader and dumper
- Version: 1.36 on 2025-10-10, with 18 votes
- Previous CPAN version: 1.35 was before
- Author: TODDR
This is the weekly favourites list of CPAN distributions. Votes count: 67
Week's winner: perl (+3)
Build date: 2025/10/12 06:49:03 GMT
Clicked for first time:
- Attribute::Validate - Validate subroutine parameters.
- Config::Writer - module to write configuration files in an easy and safe way
- Crypt::Passphrase::Bcrypt - A bcrypt encoder for Crypt::Passphrase
- Error::Show - Locate and Diagnose Errors/Exceptions in Perl programs
- Geo::Coder::US::Census - Provides a Geo-Coding functionality for the US using https://geocoding.geo.census.gov
- Mojolicious::Plugin::CSRF - Cross Site Request Forgery (CSRF) "prevention" Mojolicious plugin
- MooX::Role::SEOTags - A role for generating SEO meta tags (OpenGraph, Twitter, etc)
- NetBox::API - perl interface to NetBox API
- NetBox::Client - perl interface to NetBox API
- Nix::Proc::Meminfo - access /proc/meminfo with core classes
- POE::Component::JobQueue - POE component for processing large numbers of tasks with finite numbers of workers.
- SPVM::Go - Goroutines of The Go Programming Language
- WWW::Shorten::SnipURL - Perl interface to http://SnipURL.com
Increasing its reputation:
- Apache2::API (+1=2)
- App::perlhl (+1=3)
- B::Keywords (+1=8)
- Bio::EnsEMBL (+1=2)
- Class::Load (+1=33)
- DBIx::Sunny (+1=9)
- Devel::PatchPerl (+1=8)
- Dist::Zilla::Plugin::Covenant (+1=2)
- Dist::Zilla::Plugin::Test::Perl::Critic (+1=11)
- Encode (+1=65)
- EV (+1=49)
- File::Find::Rule (+1=40)
- Furl (+1=46)
- Future::AsyncAwait (+1=50)
- Geo::Coder::OSM (+1=5)
- Geo::StreetAddress::US (+1=3)
- IO (+1=68)
- IO::All (+1=70)
- JSON::PP (+1=21)
- JSON::XS (+1=120)
- Lingua::EN::Inflexion (+1=10)
- Math::Symbolic (+1=5)
- MCP (+1=3)
- Module::Generic (+1=3)
- Mojolicious::Plugin::Inertia (+1=2)
- Mojolicious::Plugin::MountPSGI (+1=2)
- Moo (+1=307)
- Moose (+1=334)
- MooX::Role::Parameterized (+1=2)
- MRO::Compat (+1=4)
- Net::SFTP::Foreign (+1=26)
- Otogiri (+1=5)
- Package::Variant (+1=7)
- Path::Tiny (+1=192)
- perl (+3=438)
- Perl::Tidy (+1=144)
- SPVM::Mojolicious (+1=2)
- SQL::Maker (+1=13)
- SQL::QueryMaker (+1=3)
- Sub::Infix (+1=5)
- Test::Class::Moose (+1=18)
- Test::Perl::Critic (+1=15)
- Test::Simple (+1=198)
- Text::Unidecode (+1=36)
- Time::Piece (+1=64)
- Tk (+1=36)
- Types::Algebraic (+1=4)
- URI::db (+1=11)
- UUID (+1=7)
- UUID::Tiny (+1=13)
- WebService::Ollama (+1=2)
- WWW::Shorten (+1=2)
Class::Mite is getting better relatively as numbers shown in the post below: https://theweeklychallenge.org/blog/bless-vs-class-mite

Dave writes:
Last month I completed rewriting and modernising perlxs.pod, Perl's reference manual for XS. It's now sitting as PR #23795, and will hopefully get merged before too long. (I actually completed the work two days into October, so next month's report will show a few hours.)
From the PR's description:
This branch completely rewrites and modernises the XS reference manual, perlxs.pod.
The new file is about twice the size of the old one.
This branch:
- deletes some obsolete sections;
- reorders the existing sections into a more logical order;
- adds a large new introductory/overview part, which explains all the background needed to understand what XSUBs do, including SVs, the stack, reference counts, magic etc.
- includes a BNF syntax section
- modernises: e.g. it uses ANSI parameter syntax throughout
- has a fully-worked example using T_PTROBJ
SUMMARY:
- 23:19 modernise perlxs.pod
3:50 rationalise XS typemap file search order
Total:
- 27:09 (HH::MM)

Tony writes: ``` [Hours] [Activity] 2025/09/01 Monday 0.48 #23641 testing, comment 0.77 #23659 review and comment 0.20 #23659 review updates and comment 0.83 reply DB.pm discussion 0.25 #23648 try to reproduce and comment
0.47 #23659 review and approve
3.00
2025/09/02 Tuesday 0.60 #23627 follow-up, review and comment 0.53 #23616 research and comment 0.23 #23669 review, look for a similar test, comment 0.43 #23641 testing, follow-up 0.32 #23648 follow-up 0.32 #23667 review, comment 0.57 #23668 review, testing, comment and approve
0.85 #13307/#23661 testing, comment on both
3.85
2025/09/03 Wednesday 0.12 #23668 review updates and reapprove 0.57 #23661 review discussion and consider, request changes 0.18 #23616 review and approve 0.33 #23627 review updates and approve
0.62 #23669 testing and comments
1.82
2025/09/04 Thursday 0.15 #23661 review changes, test results and comment 1.07 #23573 testing, approve with comment 0.13 #23669 review updates and approve 0.52 #23641 review, research and comment 0.15 #23638 check latest changes and approve 0.37 #23680 review and approve 0.17 #23680 review updates and still approved 0.18 #23682 review, check test failures and comment
0.33 #23679 review
3.07
2025/09/05 Friday
0.42 #23680 follow-up
0.42
2025/09/08 Monday 1.07 #23641 review, research, comments 0.08 #23670 review and approve 0.55 #23671 review, research and approve with comment 0.08 #23672 review and approve 0.18 #23678 review and comment 0.22 #23683 review and comment
2.62 test_pl.pod - make a start
4.80
2025/09/09 Tuesday 0.82 #23679 review and comment 1.48 test_pl.pod - more
1.78 test_pl.pod - more
4.08
2025/09/10 Wednesday 1.30 #23690 review, testing and comment 0.15 #23691 review and approve 0.70 #23694 review and approve
1.42 test_pl.pod - mostly done, needs review
3.57
2025/09/11 Thursday 0.48 #23695 review and comment 0.12 #23696 review and approve 0.10 #23697 review and approve 1.48 #23698 review and comments
1.38 test_pl.pod, add and test example, make PR 23700
3.56
2025/09/15 Monday 0.25 #23700 follow-up 1.57 #23690 review updates, testing, research and comment 0.32 #23695 review updates and approve 1.00 #23641 testing, research and comment
0.92 test_pl.pod - some missing stuff, don’t talk about TAP
4.06
2025/09/16 Tuesday 0.42 #23698 review updated PR and approve 0.73 test_pl.pod - minor fixes, add to podcheck 0.28 #23678 review and comment 0.80 #23702 review
0.92 #23702 try to understand line-breaking
3.15
2025/09/17 Wednesday 0.17 #23702 comment 1.50 #23678 try to understand the threads watchdog 1.00 #23678 debugging, testing and comment
0.65 #23715 review and comments
3.32
2025/09/18 Thursday 0.12 #23717 review and approve 0.27 #23718 review and approve 0.42 #23719 review and approve 0.32 #23720 review, research and comment 0.17 #23721 review and approve 0.52 #23714 review, research and comment 0.42 #23720 research and follow-up
1.48 test.pl/test-dist-modules.pl clean up, testing, fixes
3.72
2025/09/22 Monday 0.42 #23720 review updates and approve 0.22 #23714 comment 0.63 #23747 review and comment 0.58 #23731 review and comment 0.52 #23730 review, research and comment 0.72 #23728 review, research and comments 0.12 #23734 review and approve 0.13 #23735 review and approve 0.15 #23736 review and approve with comment 0.33 #23742 review and approve 0.42 #23743 review and approve 0.13 #23749 review and approve
0.12 #23746 review and approve
4.49
2025/09/23 Tuesday 0.70 #23731 review updates and approve 0.25 #23752 review and comment 0.32 #23754 review and approve 0.22 #23747 review update and approve 0.17 #23725 review and briefly comment
0.27 #23752 review update and comment
1.93
2025/09/24 Wednesday 0.82 #23710 review and approve 0.25 #23076 review discussion and change and approve 0.83 #23761 review, research and approve 1.22 #23753 look into CI failure, testing, fixes start a full test 0.28 #23753 clean up and push to PR for CI
0.37 #23757 review and approve
3.77
2025/09/25 Thursday 0.60 github notifications 0.23 #23753 comment and approve 1.05 #23759 review, check cpan, testing, approve with comment 0.17 #23763 review and comment 0.08 #23765 review and approve 0.08 #23766 review and approve 0.08 #23767 review and comment 0.82 #21877 write tests, work on re-work
0.45 #21877 more re-work
3.56
2025/09/29 Monday
3.27 #23641 review, testing
3.27
2025/09/30 Tuesday 1.28 #23782 review and comments 0.35 #23779 review and approve
0.63 #23641 more testing and comment
2.26
Which I calculate is 61.7 hours.
Approximately 63 tickets were reviewed or worked on. ```

Paul writes:
This month I spent mostly tidying up various bits of fallout from last
month's OP_MULTIPARAM
work towards signatures, and also got the named
parameters branch (just-about) ready for review and merge, along with a
few other bugfixes.
- 1 = BBC ticket
meta
- https://github.com/Perl/perl5/issues/23675
- 2 = Bugfix
parse_subsignature()
on empty parens- https://github.com/Perl/perl5/issues/17689
- 2 = BBC ticket
XS-Parse-Sublike
- https://github.com/Perl/perl5/issues/23674
- 5.5 = Improvements to
B::Deparse
of on signatures- https://github.com/Perl/perl5/issues/23699
- https://github.com/Perl/perl5/pull/23710
- 1 = BBC ticket
Syntax-Keyword-MultiSub
- https://github.com/Perl/perl5/issues/23712
- 1 = BBC ticket
Future-AsyncAwait
- https://github.com/Perl/perl5/issues/23711
- 1 = Continue work on signature-named-params branch
- https://github.com/leonerd/perl5/tree/signature-named-parameters
- 4 = COP warnings API additions
- https://github.com/Perl/perl5/pull/23731
- 1 = BBC ticket
Syntax-Keyword-Try
- https://github.com/Perl/perl5/issues/23609
- 1 = Bugfix module ends in ADJUST block
- https://github.com/Perl/perl5/issues/23758
- 2 = Modernize
attributes.pm
to use v5.40- https://github.com/Perl/perl5/pull/23769
- 2 = Bugfix fieldinfo during thread cloning
- https://github.com/Perl/perl5/issues/23771
- 1 = Other github code reviews
Total: 24.5 hours
Available from the Wiki Haven.
I have still not had time to update CPAN::MetaCustodian, so it does not yet work correctly with the latest version of Perl.Wiki.html.
• Convert Perl scripts to Python while maintaining performance and functionality
• Debug and optimize converted Python scripts
• Collaborate with team members to meet project requirements
• Document the conversion process and provide knowledge transfer
________________________________________
Technical Skills:
Perl (5+ years):
• Strong understanding of Perl syntax, modules, and libraries
• Experience with regular expressions, file handling, and data manipulation
• Familiarity with legacy Perl scripts and debugging
Python (2+ years):
• Strong understanding of Python syntax, libraries, and frameworks
• Experience with Python’s re module, file handling, and data manipulation
• Knowledge of Python best practices (PEP 8 standards)
Code Conversion:
• Hands-on experience converting Perl scripts to Python
• Ability to map Perl constructs (scalars, arrays, hashes) to Python equivalents (variables, lists, dictionaries)
• Understanding of differences in error handling, string manipulation, and OOP between Perl and Python
________________________________________
Soft Skills:
• Analytical Thinking: Ability to understand complex Perl scripts before conversion
• Attention to Detail: Ensure converted Python scripts maintain original functionality
• Communication: Document and explain conversion process to stakeholders
• Collaboration: Work closely with developers, testers, and stakeholders
For quite some I wanted to write a small static image gallery so I can share my pictures with friends and family. Of course there are a gazillion tools like this, but, well, sometimes I just want to roll my own.
I took the opportunity during our stay in Schwaz to take a few hours and hack together snig, the (small | static | simple | stupid | ...) image gallery. Here you can see the example gallery (showing some of the pictures I took in Schwaz).
I used the old, well tested technique I call brain coding0, where you start with an empty vim buffer and type some code (Perl, HTML, CSS) until you're happy with the result. It helps to think a bit (aka use your brain) during this process.
According to my timetracker I spend 8h 15min (probably half of it spend fiddling with CSS...).
Installation
I used the new Perl class feature, so you'll need at least Perl 5.40 which was released last year and is included in current Debian.
I prefer cpm to install CPAN modules:
cpm install -g Snig
I haven't provided a Containerfile
yet, but if somebody is interested, drop me a line.
You can get the raw source code from Source Hut (as I don't want to support the big LLM vacuum machines formerly known as Git(Hub|Lab)).
Example usage
You need a folder filled with images (eg some-pictures
) and some place where you can host a folder and a bunch of HTML files.
ls -la some-pictures/
7156 -rw------- 1 domm domm 7322112 Oct 6 09:14 P1370198.JPG
7188 -rw------- 1 domm domm 7354880 Oct 6 09:14 P1370208.JPG
7204 -rw------- 1 domm domm 7369728 Oct 6 09:14 P1370257.JPG
Then you do
snig.pl --input some-pictures --output /var/web/my-server-net/gallery/2025-10-some-pictures --name "Some nice pictures"
This will:
- find all
jpgs
in the foldersome-pictures
- copy them into the output folder
- generate a thumbnail (for use in the list)
- generate a preview (for use in the detail page)
- generate a HTML overview page
- generate a HTML detail page for each image, linked to the next/prev image
- generate a zip archive of the images
- EXIF rotation hints are used to properly orient the previews
- images are sorted by the EXIF timestamp (per default, you could also use mtime)
ls -la /var/web/my-server-net/gallery/2025-10-some-pictures
-rw-rw-r-- 1 domm domm 1370 Sep 26 21:15 20250914_p1370198.html
-rw-rw-r-- 1 domm domm 1370 Sep 26 21:15 20250914_p1370208.html
-rw-rw-r-- 1 domm domm 1370 Sep 26 21:15 20250915_p1370253.html
-rw-rw-r-- 1 domm domm 171738640 Sep 26 21:12 2025-10-some-pictures.zip
-rw-rw-r-- 1 domm domm 3977 Sep 26 21:15 index.html
-rw-rw-r-- 1 domm domm 7322112 Sep 26 21:12 orig_20250914_P1370198.JPG
-rw-rw-r-- 1 domm domm 7354880 Sep 26 21:12 orig_20250914_P1370208.JPG
-rw-rw-r-- 1 domm domm 7686656 Sep 26 21:12 orig_20250915_P1370253.JPG
-rw-rw-r-- 1 domm domm 106382 Sep 26 21:12 preview_20250914_P1370198.JPG
-rw-rw-r-- 1 domm domm 170346 Sep 26 21:12 preview_20250914_P1370208.JPG
-rw-rw-r-- 1 domm domm 133342 Sep 26 21:12 preview_20250915_P1370253.JPG
-rw-rw-r-- 1 domm domm 1434 Sep 26 21:15 snig.css
-rw-rw-r-- 1 domm domm 5128 Sep 26 21:12 thumbnail_20250914_P1370198.JPG
-rw-rw-r-- 1 domm domm 9495 Sep 26 21:12 thumbnail_20250914_P1370208.JPG
-rw-rw-r-- 1 domm domm 6408 Sep 26 21:12 thumbnail_20250915_P1370253.JPG
You can then take the output folder and rsync it onto a static file server. Or install snig
on your web server and do the conversion there..
Again, take a look at the example gallery.
Limitations
For now, snig
is very simple and stupid, so quite a few things are still hard coded (like copyright and license). If somebody else wants to use it, I will add some more command line flags and/or a config file. But for now this is not needed, so I did not add it.
Have fun
I had quite some fun hacking snig
and also used it as an opportunity to learn the new class
syntax (and fresh up on subroutine / method signatures). So not only did I use my brain, I actually learned something new!
Please feel free to give snig
a try. Or just use one of the countless other static image gallery generators. Or just write your own!
lobste.rs, hacker news, reddit
Footnotes
0 As opposed to vibe coding.
-
App::DBBrowser - Browse SQLite/MySQL/PostgreSQL databases and their tables interactively.
- Version: 2.433 on 2025-09-29, with 17 votes
- Previous CPAN version: 2.432 was 2 days before
- Author: KUERBIS
-
App::Netdisco - An open source web-based network management tool.
- Version: 2.091000 on 2025-09-30, with 760 votes
- Previous CPAN version: 2.090002 was 5 days before
- Author: OLIVER
-
Dist::Zilla::Plugin::Git - Update your git repository after release
- Version: 2.052 on 2025-09-28, with 44 votes
- Previous CPAN version: 2.051 was 1 year, 3 months, 12 days before
- Author: ETHER
-
JSON::Schema::Modern - Validate data against a schema using a JSON Schema
- Version: 0.619 on 2025-09-28, with 12 votes
- Previous CPAN version: 0.618 was 21 days before
- Author: ETHER
-
Mojo::Pg - Mojolicious ♥ PostgreSQL
- Version: 4.28 on 2025-09-29, with 98 votes
- Previous CPAN version: 4.27 was 3 years, 6 months, 14 days before
- Author: SRI
-
SPVM - The SPVM Language
- Version: 0.990093 on 2025-09-30, with 36 votes
- Previous CPAN version: 0.990092 was 4 days before
- Author: KIMOTO
The Experiment
At the beginning of the year, we ran a small experiment at work. We hired four annotators and let them rate 900 sentences (the details are not important). To decide whether the inter-annotator agreement was significant, we calculated (among others) Krippendorff’s alpha coefficient.
I’d used Perl for everything else in the project, so I reached for Perl to calculate the alpha, as well. I hadn’t found any module for it on CPAN, so I wrote one: I read the Wikipedia page and implemented the formulas.
The Real Data
The experiment was promising, so we got additional funding. We hired 3 more annotators, and a few months later, another nine. This increased the number of raters to 16. So far, they’ve rated about 200K sentences. Each sentence has been annotated by at least two annotators (usually three).
One day, I decided to calculate the inter-annotator agreement for the new data. To my surprise, the calculation took more than 6 hours.
Profiling
I ran the NYTProf profiler on a smaller dataset to discover the problematic areas of the code. Browsing the results I clearly identified the culprit: the method responsible for building the coincidence matrix.
sub _build_coincidence($self) {
my %coinc;
my @s = $self->vals;
for my $v (@s) {
for my $v_ (@s) {
$coinc{$v}{$v_} = sum(map {
my $unit = $_;
my @k = keys %$unit;
sum(0,
map {
my $i = $_;
scalar grep $unit->{$_} eq $v_,
grep $i ne $_, @k
} grep $unit->{$_} eq $v, @k
) / (@k - 1)
} @{ $self->units });
}
}
return \%coinc
}
You can see the four nested loops here (two for
’s and two map
’s). I apologise, that’s how I’d understood the formula.
The Solution Suggested by AI
“This is the right time to experiment with AI!” thought I to myself. I started a session with GPT-4o, showed it the whole module and asked for a more efficient version. The LLM correctly identified the four nested loops, suggested an improvement, and generated code to implement it.
Unfortunately, the code didn’t even compile. I asked the AI several times to fix the problems, until I got a runnable code, but its running time wasn’t different to the original implementation. So I asked it to try harder, and after a third iteration of the whole ordeal, we had the following code that ran for 20 seconds instead of 22:
sub _build_coincidence($self) {
my %coinc;
my @vals = $self->vals;
# Initialize coincidence counts
for my $v (@vals) {
for my $v_ (@vals) {
$coinc{$v}{$v_} = 0;
}
}
# Iterate over each unit
for my $unit (@{ $self->units }) {
my @keys = keys %$unit;
my $unit_count = @keys;
# Count occurrences of each value in the current unit
my %value_count;
$value_count{ $unit->{$_} }++ for @keys;
# Calculate coincidences based on the value counts
for my $v (@vals) {
for my $v_ (@vals) {
if (exists $value_count{$v} && exists $value_count{$v_}) {
my $coinc_count = 0;
# Count pairs of keys that match the values
for my $key1 (@keys) {
for my $key2 (@keys) {
next if $key1 eq $key2; # Skip same keys
if ($unit->{$key1} eq $v && $unit->{$key2} eq $v_) {
$coinc_count++;
}
}
}
# Update the coincidence count
$coinc{$v}{$v_} += $coinc_count / ($unit_count - 1) if $unit_count > 1;
}
}
}
}
return \%coinc
}
That’s many lines. We’ve got comments, which should be nice, but… For the large data, I’d still have had to wait for several hours.
The Optimisation
I decided to bite the bullet and tried to optimise the code myself. I went over both the original code and the suggested improvement to realise the LLM wanted to cache the number of values in %value_count
but never really used the value: it only checked for existence.
What was really needed was not to iterate over all the values every time skipping the irrelevant ones, but to precompute the relevant values and reduce the breadth of the loops. Usually, there were only two or three values per unit (each sentence was annotated by at least two annotators), so there was no reason to check all sixteen possible.
This is the code I wrote and released as the new version of the module. For the large dataset, the running time dropped from six hours to four seconds.
sub _build_coincidence($self) {
my @vals = $self->vals;
my %coinc;
@{ $coinc{$_} }{@vals} = (0) x @vals for @vals;
for my $unit (@{ $self->units }) {
my %is_value;
@is_value{ values %$unit } = ();
my @values = keys %is_value;
my @keys = keys %$unit;
for my $v (@values) {
for my $v_ (@values) {
my $coinc_count = 0;
for my $key1 (@keys) {
for my $key2 (@keys) {
next if $key1 eq $key2;
++$coinc_count
if $unit->{$key1} eq $v
&& $unit->{$key2} eq $v_;
}
}
$coinc{$v}{$v_} += $coinc_count / (@keys - 1);
}
}
}
return \%coinc
}
I showed my final code to the LLM. It congratulated me on the brilliant solution, emphasising some of the tricks I’d used to make it faster (e.g. filling the matrix by zeros using the repetition operator).
Did it help me? I’m not sure. It motivated me to study the code and find the solution, but it didn’t really contribute to it. It took me about two hours including finding the solution myself.
What’s your experience with AI?
For the second consecutive year, The Perl and Raku Foundation (TPRF) is overjoyed to announce a donation of USD 25,000 from DuckDuckGo.
DuckDuckGo has demonstrated how Perl and its ecosystem can deliver power and scale to drive the DuckDuckGo core systems, plug-in framework and Instant Answers. The Foundation is grateful that DuckDuckGo recognises the importance of Perl, and for their generous funding support for a second year through their charitable donations programme.
– Stuart J Mackintosh, President of The Perl and Raku Foundation
Last year’s donation of USD 25,000 from DuckDuckGo was instrumental in helping to fund the foundation’s Core Perl Maintenance Fund and this year’s donation will help to fund more of the same crucial work that keeps the Perl language moving forward.
Paul “LeoNerd” Evans is one of the developers who gets regular funding from the Perl Core Maintenance Fund. Here is a short list of just some of the many contributions which Paul has made to core Perl as part of the maintenance fund work:
- The builtin module (5.36), making available many new useful language-level utilities that were previously loaded from modules like Scalar::Util
- The complete feature ‘class’ system (5.38), adding proper object-orientation syntax and abilities
- Lexical method support (5.42), adding
my method
and the$obj->&method
invocation syntax for better object encapsulation - Stabilising some of the recent experiments - signatures (5.36), try/catch (5.40), foreach on multiple vars (5.40)
- Ability to use the //= and ||= operators in signatures (5.38), performance improvements and named parameters (upcoming in next release)
- The new any and all keywords (5.42)
We look forward to many more innovative contributions from Paul over the coming year.
While TPRF never takes continued support for granted, when it does arrive, it allows the foundation to plan for the future with much greater confidence. Multi-year partnerships with our sponsors allow us to continue to prioritize important work, knowing that we will have the runway that we need to fund the work which helps to sustain the Perl Language and its associated communities.
For more information on how to become a sponsor, please contact: olaf@perlfoundation.org
"Fireworks" by colink. is licensed under CC BY-SA 2.0.
A few of my recent projects—like Cooking Vinyl Compilations and ReadABooker—aim to earn a little money via affiliate links. That only works if people actually find the pages, share them, and get decent previews in social apps. In other words: the boring, fragile glue of SEO and social meta tags matters.
As I lined up a couple more sites in the same vein, I noticed I was writing very similar code again and again: take an object with title
, url
, image
, description
, and spray out the right <meta>
tags for Google, Twitter, Facebook, iMessage, Slack, and so on. It’s fiddly, easy to get 80% right, and annoying to maintain across projects. So I pulled it into a small Moo role—MooX::Role::SEOTags
—that any page-ish class can consume and just emit the right tags.
What are these tags and why should you care?
When someone shares your page, platforms read a handful of standardised tags to decide what to show in the preview:
- Open Graph (
og:*
) — The de-facto standard for title, description, URL, image, and type. Used by Facebook, WhatsApp, Slack, iMessage and others. - Twitter Cards (
twitter:*
) — Similar idea for Twitter/X; the common pattern istwitter:card=summary_large_image
plus title/description/image. - Classic SEO tags —
<title>
,<meta name="description">
, and a canonical URL tell search engines what the page is about and which URL is the “official” one.
MooX::Role::SEOTags gives you one method that renders all of that, consistently, from your object’s attributes.
For more information about OpenGraph, see ogp.me.
What it does
MooX::Role::SEOTags
adds a handful of attributes and helper methods so any Moo (or Moose) class can declare the bits of information that power social previews and search snippets, then render them as HTML.
- Open Graph tags (
og:title
,og:type
,og:url
,og:image
, etc.) - Twitter Card tags (
twitter:card
,twitter:title
,twitter:description
,twitter:image
) - Standard SEO:
<title>
, metadescription
, canonical<link rel="canonical">
- A single method to render the whole block with one call
- But also individual methods to give you more control over tag placement
That’s the whole job: define attributes → get valid tags out.
Quick start
Install the role using your favourite CPAN module installation tool.
cpanm MooX::Role::SEOTags
Then, in your code, you will need to add some attributes or methods that define the pieces of information the role needs. The role requires four pieces of information – og_title
, og_description
, og_url
and og_type
– and og_image
is optional (but highly recommended).
So a simple class might look like this:
package MyPage; use Moo; with 'MooX::Role::SEOTags'; # minimal OG fields has og_title => (is => 'ro', required => 1); has og_type => (is => 'ro', required => 1); # e.g. 'article' has og_url => (is => 'ro', required => 1); has og_description => (is => 'ro'); # optional niceties has og_image => (is => 'ro'); # absolute URL has twitter_card => (is => 'ro', default => sub { 'summary_large_image' }); 1;
And then you create the object:
my $page = MyPage->new( og_title => 'How to Title a Title', og_type => 'article', og_url => 'https://example.com/post/title', og_image => 'https://example.com/img/hero.jpg', og_description => 'A short, human description of the page.', );
Then you can call the various *_tag
and *_tags
methods to get the correct HTML for the various tags.
The easiest option is to just produce all of the tags in one go:
say $page->tags;
But, for more control, you can call individual methods:
say $page->title_tag; say $page->canonical_tag; say $page->og_tags; # etc...
Depending on which combination of method calls you use, the output will look something like this:
<title>How to Title a Title</title> <meta name="description" content="A short, human description of the page."> <link rel="canonical" href="https://example.com/post/title"> <meta property="og:title" content="How to Title a Title"> <meta property="og:type" content="article"> <meta property="og:url" content="https://example.com/post/title"> <meta property="og:image" content="https://example.com/img/hero.jpg"> <meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="How to Title a Title"> <meta name="twitter:description" content="A short, human description of the page."> <meta name="twitter:image" content="https://example.com/img/hero.jpg">
In many cases, you’ll be pulling the data from a database and displaying the output using a templating system like the Template Toolkit.
my $tt = Template->new; my $object = $resultset->find({ slug => $some_slug }); $tt->process('page.tt', { object => $object }, "$some_slug/index.html");
In this case, you’d just add a single call to the <head> of your page template.
<head> <!-- lots of other HTML --> [% object.tags %] </head>
A note if you used my earlier Open Graph role
If you spotted MooX::Role::OpenGraph
arrive on MetaCPAN recently: SEOTags
is the “grown-up” superset. It does Open Graph and Twitter and standard tags, so you only need one role. The old module is scheduled for deletion from MetaCPAN.
SEO tags and JSON-LD
These tags are only one item in the SEO toolkit that you’d use to increase the visibility of your website. Another useful tool is JSON-LD – which allows you to add a machine-readable description of the information that your page contains. Google loves JSON-LD. And it just happens that I have another Moo role called MooX::Role::JSON_LD which makes it easy to add that to your page too. I wrote a blog post about using that earlier this year.
In conclusion
If you’ve got even one page that deserves to look smarter in search and social previews, now’s the moment. Pick a page, add a title, description, canonical URL and a decent image, and let MooX::Role::SEOTags
spit out the right tags every time (and, if you fancy richer results, pair it with MooX::Role::JSON_LD
). Share the link in Slack/WhatsApp/Twitter to preview it, fix anything that looks off, and ship. It’s a 20-minute tidy-up that can lift click-throughs for years—so go on, give one of your pages a quick SEO spruce-up today.
The post Easy SEO for lazy programmers first appeared on Perl Hacks.
TL;DR
I built a dynamic workspace loader for i3wm that lets me switch between multiple context-aware workspace groups (like “Client”, “Company”, and “Personal”) without losing state. It uses Perl, AnyEvent::I3, YAML config, and tick events to load layouts and launch apps on demand.
Workspaces on Demand
I have been using i3 as a Window Manager for years now. And while I love working with i3, there is one thing I found annoying. And that is that it lacks context awareness. What do I mean by this? I use it in several contexts: “Client”, “Company”, and “Personal”. Meaning, I’m in either client mode, company mode or personal mode. And ‘client mode’ can mean working with one or multiple clients at once. I pitched my initial idea on i3’s discussion pages and someone else asked about something similar as KDE’s “Activities”.