<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: LNATION</title>
    <description>The latest articles on DEV Community by LNATION (@lnation).</description>
    <link>https://dev.to/lnation</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3227216%2Fa5ec6415-a303-4e9c-9146-1e2e77e43cb3.png</url>
      <title>DEV Community: LNATION</title>
      <link>https://dev.to/lnation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lnation"/>
    <language>en</language>
    <item>
      <title>Enums for Perl: Adopting Devel::CallParser and Building Enum::Declare</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Tue, 14 Apr 2026 10:15:35 +0000</pubDate>
      <link>https://dev.to/lnationorg/enums-for-perl-adopting-develcallparser-and-building-enumdeclare-28bj</link>
      <guid>https://dev.to/lnationorg/enums-for-perl-adopting-develcallparser-and-building-enumdeclare-28bj</guid>
      <description>&lt;h2&gt;
  
  
  Adopting one from Zefram
&lt;/h2&gt;

&lt;p&gt;Back in 2011, Andrew Main, known to the Perl community as Zefram, released &lt;code&gt;Devel::CallParser&lt;/code&gt;. It was a quiet piece of infrastructure: a C API that let XS modules attach custom argument parsers to Perl subroutines. Where the core's &lt;code&gt;PL_keyword_plugin&lt;/code&gt; API was awkward to work with directly, CallParser gave you a structured way to extend Perl's syntax from C.&lt;/p&gt;

&lt;p&gt;Zefram maintained it through 2013, fixing compatibility issues with &lt;code&gt;indirect&lt;/code&gt; and &lt;code&gt;Sub::StrictDecl&lt;/code&gt;, working around padrange optimiser changes, and shipping version 0.002. Then silence. The module sat on CPAN, unmaintained, while Perl kept moving.&lt;/p&gt;

&lt;p&gt;By the current year and perl version it was breaking. I personally could not install it locally on my hardened macOS runtimes, many reports of issues on threaded builds, shifted &lt;code&gt;qerror&lt;/code&gt; internals, and many red reports on CPAN testers. I needed CallParser for &lt;code&gt;Object::Proto::Sugar&lt;/code&gt;, so I adopted it and so far have shipped six dev releases (&lt;code&gt;0.003_01&lt;/code&gt; through &lt;code&gt;0.003_06&lt;/code&gt;) to try get it passing green again on all envs. Not glamorous work, but Zefram built something worth preserving.&lt;/p&gt;

&lt;p&gt;(RIP Zefram... I didn't know them personally but the infrastructure they left behind is still making new things possible.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The Idea
&lt;/h2&gt;

&lt;p&gt;With CallParser working again, I decided to implement an idea I'd thought about for a long time: give Perl a proper &lt;code&gt;enum&lt;/code&gt; keyword.&lt;/p&gt;

&lt;p&gt;Not a hash. Not a bunch of &lt;code&gt;use constant&lt;/code&gt; lines. Not a class/object pretending to be an enumeration. An actual keyword that declares an enum at compile time, generates real constants, and gives you a meta object for introspection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enum::Declare
&lt;/h2&gt;

&lt;p&gt;Here's what it looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Enum::&lt;/span&gt;&lt;span class="nv"&gt;Declare&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;enum&lt;/span&gt; &lt;span class="nv"&gt;Colour&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;Red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Blue&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;Red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;# 0&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;Green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;code&gt;enum&lt;/code&gt; is a real keyword, parsed at compile time by an XS callback wired through &lt;code&gt;cv_set_call_parser&lt;/code&gt;. The constants are true constants, not subroutine calls, not tied variables. The compiler sees them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicit Values
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;enum&lt;/span&gt; &lt;span class="nv"&gt;HttpStatus&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;OK&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Created&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;NotFound&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Internal&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  String Enums
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;enum&lt;/span&gt; &lt;span class="nv"&gt;LogLevel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;Str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Warn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
    &lt;span class="nv"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Fatal&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# "debug"&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# "warning"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an explicit value, &lt;code&gt;:Str&lt;/code&gt; lowercases the constant name. With one, it uses what you gave it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bitflags
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;enum&lt;/span&gt; &lt;span class="nv"&gt;Perms&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;Flags&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Execute&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$rw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Read&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can read&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$rw&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can write&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$rw&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;:Flags&lt;/code&gt; assigns powers of two automatically. Combine them with bitwise operators as you'd expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exporting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# In your module:&lt;/span&gt;
&lt;span class="nv"&gt;enum&lt;/span&gt; &lt;span class="nv"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;Export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;OK&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;NotFound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Consumers get the constants automatically, or use tags:&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;MyModule&lt;/span&gt; &lt;span class="sx"&gt;qw(:StatusCode)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Meta Objects
&lt;/h3&gt;

&lt;p&gt;Every enum gets a meta object accessible by calling the enum name as a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Colour&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;# 3&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;# "Red"&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Blue&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;   &lt;span class="c1"&gt;# 2&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;# true&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@pairs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;pairs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# (Red =&amp;gt; 0, Green =&amp;gt; 1, Blue =&amp;gt; 2)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exhaustive Matching
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;Colour&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;Red&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;Green&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;go&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;Blue&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sky&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Miss a variant and it dies. Every key/branch must be covered.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Under the hood, &lt;code&gt;use Enum::Declare&lt;/code&gt; installs an XS stub named &lt;code&gt;enum&lt;/code&gt; into the calling package, then attaches a custom parser via &lt;code&gt;cv_set_call_parser&lt;/code&gt;. When Perl encounters &lt;code&gt;enum&lt;/code&gt; during compilation, the parser callback fires and:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the enum name (&lt;code&gt;lex_read_ident&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Reads optional attributes - &lt;code&gt;:Str&lt;/code&gt;, &lt;code&gt;:Flags&lt;/code&gt;, &lt;code&gt;:Export&lt;/code&gt;, &lt;code&gt;:Type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reads the &lt;code&gt;{ Name = Value, ... }&lt;/code&gt; variant block&lt;/li&gt;
&lt;li&gt;Builds the constant subs and enum data structures&lt;/li&gt;
&lt;li&gt;Installs the meta object&lt;/li&gt;
&lt;li&gt;Optionally wires up &lt;code&gt;@EXPORT&lt;/code&gt; / &lt;code&gt;@EXPORT_OK&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of this happens at compile time. By the time Perl starts executing your code, the constants exist, the meta object is ready, and the exports are in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enum::Declare::Common
&lt;/h2&gt;

&lt;p&gt;Once the keyword worked, the obvious next step was a library of common enums:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Enum::Declare::Common::&lt;/span&gt;&lt;span class="nv"&gt;HTTP&lt;/span&gt; &lt;span class="sx"&gt;qw(:StatusCode :Method)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Enum::Declare::Common::&lt;/span&gt;&lt;span class="nv"&gt;Calendar&lt;/span&gt; &lt;span class="sx"&gt;qw(:Weekday :Month)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Enum::Declare::Common::&lt;/span&gt;&lt;span class="nv"&gt;Color&lt;/span&gt; &lt;span class="sx"&gt;qw(:CSS)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;# 200&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;# "get"&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;Monday&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;January&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Enum::Declare::Common&lt;/code&gt; ships 20 submodules covering HTTP status codes and methods, ISO country and currency codes, MIME types, 148 named CSS hex colours, timezone offsets, Unix permissions, log levels, and more. All built on the same &lt;code&gt;enum&lt;/code&gt; keyword, all with meta objects, all exportable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integration with Object::Proto
&lt;/h2&gt;

&lt;p&gt;Every enum in the Common collection is declared with the &lt;code&gt;:Type&lt;/code&gt; attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;enum&lt;/span&gt; &lt;span class="nv"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;Type&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;Export&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;OK&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;Created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This registers the enum as a type in &lt;code&gt;Object::Proto&lt;/code&gt; at load time, so you can use enum names directly as slot types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Enum::Declare::Common::&lt;/span&gt;&lt;span class="nv"&gt;HTTP&lt;/span&gt; &lt;span class="sx"&gt;qw(:StatusCode :Method)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Enum::Declare::Common::&lt;/span&gt;&lt;span class="nv"&gt;LogLevel&lt;/span&gt; &lt;span class="sx"&gt;qw(:Level)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::&lt;/span&gt;&lt;span class="nv"&gt;Proto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;APIRequest&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;method:Method:required&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;status:StatusCode&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;log_level:Level:default(&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;Info&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;APIRequest&lt;/span&gt; &lt;span class="s"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$req&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;# valid&lt;/span&gt;
&lt;span class="nv"&gt;$req&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9999&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;# dies - not a valid StatusCode&lt;/span&gt;
&lt;span class="nv"&gt;$req&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;# coercion - resolves to OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type checks and coercions run in C via &lt;code&gt;object_register_type_xs_ex&lt;/code&gt;. No Perl callback overhead. A single pair of C functions serves every enum type, only the data pointer differs.&lt;/p&gt;

&lt;p&gt;If you're writing Perl and you've been using hashes or &lt;code&gt;use constant&lt;/code&gt; blocks to fake enums, give &lt;code&gt;Enum::Declare&lt;/code&gt; a try. In my opinion it's enums the way they should have always worked.&lt;/p&gt;

&lt;p&gt;As always if you have any questions just post below.&lt;/p&gt;

</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Learning XS - Custom Ops</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sat, 11 Apr 2026 18:34:39 +0000</pubDate>
      <link>https://dev.to/lnation/learning-xs-custom-ops-4lag</link>
      <guid>https://dev.to/lnation/learning-xs-custom-ops-4lag</guid>
      <description>&lt;p&gt;This is the next tutorial in my Learning XS series. This post explores one of Perl's most powerful optimisation techniques: &lt;strong&gt;Custom Ops&lt;/strong&gt;. We'll build a real-world example, reimplementing one of my first XS cpan modules - a Shannon entropy calculator - and explain how to bypass Perl's subroutine call overhead entirely.&lt;/p&gt;

&lt;p&gt;So firstly... What are Custom Ops?&lt;/p&gt;

&lt;p&gt;When you call a Perl subroutine, Perl executes an &lt;code&gt;entersub&lt;/code&gt; op that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sets up a new stack frame&lt;/li&gt;
&lt;li&gt;Pushes arguments onto the stack&lt;/li&gt;
&lt;li&gt;Magical things happens&lt;/li&gt;
&lt;li&gt;Jumps to the subroutine code&lt;/li&gt;
&lt;li&gt;More Magical things can happens&lt;/li&gt;
&lt;li&gt;Cleans up and returns&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For simple functions called millions of times (like mathematical operations), this overhead dominates execution time. Custom ops let you replace the entire &lt;code&gt;entersub&lt;/code&gt; call with a single, specialised op - eliminating that overhead.&lt;/p&gt;

&lt;p&gt;Next.. What is Shannon Entropy?&lt;/p&gt;

&lt;p&gt;Shannon entropy measures the information content (or "randomness") of a string:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"aaaa"&lt;/code&gt; → 0 bits (no randomness, completely predictable)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"abcd"&lt;/code&gt; → 2 bits (4 equally likely symbols = log₂(4))&lt;/li&gt;
&lt;li&gt;Random data → ~8 bits per byte (maximum entropy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's used in compression, cryptography, and password strength estimation. A perfect candidate for a small, focused XS module.&lt;/p&gt;

&lt;p&gt;In Perl the algorithm for Shannon entropy can look something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;entropy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$entropy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;%t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="nb"&gt;split&lt;/span&gt; &lt;span class="p"&gt;'',&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;values&lt;/span&gt; &lt;span class="nv"&gt;%t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nv"&gt;$entropy&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nv"&gt;$p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt; &lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; 
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$entropy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nb"&gt;log&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Distribution
&lt;/h2&gt;

&lt;p&gt;First we will need to create a new perl distribution. As we have done in the past we will use &lt;code&gt;Module::Starter&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;module-starter &lt;span class="nt"&gt;--module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Shannon::Entropy::XS"&lt;/span&gt; &lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Your Name"&lt;/span&gt; &lt;span class="nt"&gt;--email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your@email.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create a new directory called Shannon-Entropy-XS with the basic structure of a perl module.&lt;/p&gt;

&lt;p&gt;Next we will need to update the Makefile.PL so that it knows we are using XS. One simple way to do this is to add XSMULTI =&amp;gt; 1 to the WriteMakefile call. Which will tell MakeMaker to look for XS files in the lib/Shannon/Entropy/ directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ExtUtils::&lt;/span&gt;&lt;span class="nv"&gt;MakeMaker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;%WriteMakefileArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;NAME&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Shannon::Entropy::XS&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;AUTHOR&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sx"&gt;q{Your Name &amp;lt;your@email.com&amp;gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;VERSION_FROM&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/Shannon/Entropy/XS.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;XSMULTI&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;LIBS&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[''],&lt;/span&gt;
    &lt;span class="c1"&gt;# ... rest of your config&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;WriteMakefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;%WriteMakefileArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next lets update the lib/Shannon/Entropy/XS.pm file to load our XS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;Shannon::Entropy::&lt;/span&gt;&lt;span class="nv"&gt;XS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;our&lt;/span&gt; &lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="nv"&gt;XSLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nn"&gt;XSLoader::&lt;/span&gt;&lt;span class="nv"&gt;load&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;Shannon::Entropy::XS&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="nv"&gt;$VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Exporter&lt;/span&gt; &lt;span class="sx"&gt;qw(import)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;our&lt;/span&gt; &lt;span class="nv"&gt;@EXPORT_OK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;qw(entropy)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can create the XS file. Create a new file called lib/Shannon/Entropy/XS.xs and add the basic structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define PERL_NO_GET_CONTEXT
#include&lt;/span&gt; &lt;span class="cpf"&gt;"EXTERN.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"perl.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"XSUB.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;MODULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Shannon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Entropy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;XS&lt;/span&gt;  &lt;span class="n"&gt;PACKAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Shannon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Entropy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;XS&lt;/span&gt;
&lt;span class="n"&gt;PROTOTYPES&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That defines the package. If we compile this now, the basic tests added by Module::Starter will pass. To test that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;perl Makefile.PL
make &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding the Entropy Function
&lt;/h2&gt;

&lt;p&gt;With the basic structure working, lets write a test for our entropy function. Create or update t/01-test.t:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Test::&lt;/span&gt;&lt;span class="nv"&gt;More&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Shannon::Entropy::&lt;/span&gt;&lt;span class="nv"&gt;XS&lt;/span&gt; &lt;span class="sx"&gt;qw/entropy/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(''),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="p"&gt;'),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;%.3f&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;1223334444&lt;/span&gt;&lt;span class="p"&gt;')),&lt;/span&gt; &lt;span class="mf"&gt;1.846&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;0123456789abcdef&lt;/span&gt;&lt;span class="p"&gt;'),&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;sprintf&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;%.3f&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;abcdefghijklmnopqrst123456789!@£[]"&lt;/span&gt;&lt;span class="p"&gt;')),&lt;/span&gt; &lt;span class="mf"&gt;5.170&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="nv"&gt;done_testing&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we need to implement the entropy calculation. First, add the C helper functions to our XS file, before the MODULE line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define PERL_NO_GET_CONTEXT
#include&lt;/span&gt; &lt;span class="cpf"&gt;"EXTERN.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"perl.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"XSUB.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;math.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;makehist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;histlen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;histlen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;histlen&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;histlen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;histlen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;histlen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;makehist&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;histlen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;log2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;MODULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Shannon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Entropy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;XS&lt;/span&gt;  &lt;span class="n"&gt;PACKAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Shannon&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Entropy&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;XS&lt;/span&gt;
&lt;span class="n"&gt;PROTOTYPES&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ENABLE&lt;/span&gt;

&lt;span class="kt"&gt;double&lt;/span&gt;
&lt;span class="n"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;STRLEN&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SvPV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;RETVAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what exactly are we doing here? The &lt;code&gt;makehist()&lt;/code&gt; function builds a histogram of character frequencies by iterating through the string and mapping each unique character to a slot in the &lt;code&gt;hist&lt;/code&gt; array. The &lt;code&gt;chars[256]&lt;/code&gt; lookup table tracks which slot each byte value maps to, with -1 meaning "not yet seen". When we encounter a new character, we assign it the next available slot and increment &lt;code&gt;histlen&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;entropy()&lt;/code&gt; function then implements the Shannon entropy formula. For each unique character in the histogram, we calculate its probability (count / total length) and accumulate the entropy value. Empty strings return 0 immediately to avoid division by zero.&lt;/p&gt;

&lt;p&gt;Finally, the XS wrapper extracts the string from the Perl SV using &lt;code&gt;SvPV()&lt;/code&gt; (which gives us both the string pointer and its length) and calls our C function. The result is returned as a double.&lt;/p&gt;

&lt;p&gt;Now run &lt;code&gt;make test&lt;/code&gt; and our entropy tests should pass. But every call still goes through Perl's &lt;code&gt;entersub&lt;/code&gt; op. Let's optimise that with custom ops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Custom Ops
&lt;/h2&gt;

&lt;p&gt;Custom ops require Perl 5.14+. We'll add three pieces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An XOP structure (for debugging/introspection)&lt;/li&gt;
&lt;li&gt;A PP function (the actual operation)&lt;/li&gt;
&lt;li&gt;A call checker (transforms the op tree at compile time)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First we need to add some back porting as part of the API we are about to use was introduced in 5.26, after the last import add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifndef OpSIBLING
#  define OpSIBLING(o)              ((o)-&amp;gt;op_sibling)
#endif
#ifndef OpMORESIB_set
#  define OpMORESIB_set(o, sib)     ((o)-&amp;gt;op_sibling = (sib))
#endif
#ifndef OpLASTSIB_set
#  define OpLASTSIB_set(o, parent)  ((o)-&amp;gt;op_sibling = NULL)
#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can add the XOP declaration after the last endif from above add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#if PERL_VERSION &amp;gt;= 14
&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;XOP&lt;/span&gt; &lt;span class="n"&gt;entropy_xop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;XOP&lt;/code&gt; (eXtended OP) structure provides metadata - the op's name and description that appear in tools like B::Concise.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PP Function
&lt;/h2&gt;

&lt;p&gt;Every Perl op has a "pp" (push-pop) function that does the actual work. Add this after your entropy functions, inside a &lt;code&gt;#if PERL_VERSION &amp;gt;= 14&lt;/code&gt; block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#if PERL_VERSION &amp;gt;= 14
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;pp_entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dSP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOPs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;STRLEN&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SvPV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;POPs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;mPUSHn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A line by line explanation of the above code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;dSP&lt;/code&gt; - Declares the stack pointer local variable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SV *sv = TOPs&lt;/code&gt; - Gets the top of stack (our argument) without popping it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SvPV(sv, len)&lt;/code&gt; - Extracts the string and its length&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;entropy(str)&lt;/code&gt; - Calls our C function&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POPs&lt;/code&gt; - Pops the argument off the stack&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mPUSHn(result)&lt;/code&gt; - Pushes the result as a mortal (auto-freed) numeric value&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RETURN&lt;/code&gt; - Returns control to the next op in the chain&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Call Checker
&lt;/h2&gt;

&lt;p&gt;This is where the real magic happens. A &lt;strong&gt;call checker&lt;/strong&gt; is invoked at compile time when Perl sees a call to our function. We can inspect the op tree and transform it. Add this after the pp function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;entropy_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;pushop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cLISTOPx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/* Handle null op wrapper in modern Perl */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;OP_NULL&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cLISTOPx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;pushop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cLISTOPx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;argop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;argop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;nextop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;OpMORESIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;OpLASTSIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;newop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newUNOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OP_CUSTOM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;argop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_ppaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pp_entropy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;op_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So what is actually happening here? The call checker receives the &lt;code&gt;entersub&lt;/code&gt; op that Perl normally uses to call subroutines. Inside an entersub, the child ops form a linked list: &lt;code&gt;pushmark -&amp;gt; arg1 -&amp;gt; arg2 -&amp;gt; ... -&amp;gt; cv&lt;/code&gt;. We use &lt;code&gt;cLISTOPx(entersubop)-&amp;gt;op_first&lt;/code&gt; to get the first child. In modern Perl (5.22+), this is often wrapped in a null op, so we check for that and unwrap it to find the actual pushmark. Then we use &lt;code&gt;OpSIBLING()&lt;/code&gt; to navigate to our argument.&lt;/p&gt;

&lt;p&gt;Before we do anything destructive, we validate that the call matches our expected signature. The series of null checks and the &lt;code&gt;OpSIBLING(nextop)&lt;/code&gt; test ensure we have exactly one argument. If someone called &lt;code&gt;entropy($a, $b)&lt;/code&gt; with two arguments, or used the &lt;code&gt;&amp;amp;entropy&lt;/code&gt; sigil syntax, we bail out early by returning &lt;code&gt;entersubop&lt;/code&gt; unchanged - Perl falls back to the normal subroutine call mechanism.&lt;/p&gt;

&lt;p&gt;Once we've validated the call, we surgically detach the argument op from the entersub's child list. &lt;code&gt;OpMORESIB_set(pushop, nextop)&lt;/code&gt; rewires pushmark to skip over our argument, linking it directly to the CV. &lt;code&gt;OpLASTSIB_set(argop, NULL)&lt;/code&gt; disconnects argop from its siblings entirely, leaving it as a standalone op.&lt;/p&gt;

&lt;p&gt;With the argument detached, we create our custom op using &lt;code&gt;newUNOP(OP_CUSTOM, 0, argop)&lt;/code&gt;. This creates a unary op with our argument as its child. We then assign our pp function to execute when this op runs: &lt;code&gt;newop-&amp;gt;op_ppaddr = pp_entropy&lt;/code&gt;. Finally, we free the original entersub with &lt;code&gt;op_free()&lt;/code&gt; since it's no longer needed, and return our shiny new custom op to take its place in the op tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiring It Up in BOOT
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;BOOT&lt;/code&gt; section runs when the module loads. We register our custom op and install the call checker. Add this after your XS entropy function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="nl"&gt;BOOT:&lt;/span&gt;
&lt;span class="cp"&gt;#if PERL_VERSION &amp;gt;= 14
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;CV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entropy_cv&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;XopENTRY_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;entropy_xop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xop_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"entropy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;XopENTRY_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;entropy_xop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xop_desc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Shannon entropy calculation"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;Perl_custom_op_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aTHX_&lt;/span&gt; &lt;span class="n"&gt;pp_entropy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;entropy_xop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;entropy_cv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_cv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Shannon::Entropy::XS::entropy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entropy_cv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;cv_set_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entropy_cv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entropy_call_checker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;entropy_cv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first thing we do is set up the XOP metadata using &lt;code&gt;XopENTRY_set()&lt;/code&gt;. This registers the name "entropy" and a description "Shannon entropy calculation" with our custom op. These strings appear in debugging tools like B::Concise and Devel::Peek, making it much easier to understand op trees when things do go wrong.&lt;/p&gt;

&lt;p&gt;Next, &lt;code&gt;Perl_custom_op_register()&lt;/code&gt; connects our pp function to the XOP structure. This tells Perl's introspection system that whenever it encounters an &lt;code&gt;OP_CUSTOM&lt;/code&gt; with our &lt;code&gt;pp_entropy&lt;/code&gt; address, it should use our XOP metadata for display purposes.&lt;/p&gt;

&lt;p&gt;Finally, we wire up the call checker itself. &lt;code&gt;get_cv()&lt;/code&gt; retrieves the CV (code value) for our &lt;code&gt;Shannon::Entropy::XS::entropy&lt;/code&gt; function - this is the subroutine object that Perl created when it compiled the XSUB. We then call &lt;code&gt;cv_set_call_checker()&lt;/code&gt; to install our call checker on that CV. From now on, whenever Perl compiles a call to &lt;code&gt;entropy()&lt;/code&gt;, it will invoke our &lt;code&gt;entropy_call_checker&lt;/code&gt; function, giving us the chance to transform the op tree.&lt;/p&gt;

&lt;p&gt;Now run &lt;code&gt;make clean &amp;amp;&amp;amp; make test&lt;/code&gt; and all tests should still pass - but now with custom ops!&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying It Works
&lt;/h2&gt;

&lt;p&gt;You can use &lt;code&gt;B::Concise&lt;/code&gt; to see the optimised op tree. Create a simple test script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;B::&lt;/span&gt;&lt;span class="nv"&gt;Concise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Shannon::Entropy::&lt;/span&gt;&lt;span class="nv"&gt;XS&lt;/span&gt; &lt;span class="sx"&gt;qw(entropy)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nn"&gt;B::Concise::&lt;/span&gt;&lt;span class="nv"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;-exec&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;entropy&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;test&lt;/span&gt;&lt;span class="p"&gt;")&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without custom ops, you'd see &lt;code&gt;entersub&lt;/code&gt;. With them, you'll see our &lt;code&gt;entropy&lt;/code&gt; custom op directly in the tree - proof that we've eliminated the subroutine call overhead!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;B::Concise::compile(CODE(0x12b02b4f0))
1  &amp;lt;;&amp;gt; nextstate(main 1816 test.pl:4) v
2  &amp;lt;$&amp;gt; const[PV "test"] s
3  &amp;lt;0&amp;gt; entropy K/1
4  &amp;lt;1&amp;gt; leavesub[1 ref] K/REFC,1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay at this point you may or may not be asking why you would replace 9 lines of code with 111 lines... well it would be for the performance improvement you've just achieved. Take this quick benchmark as justification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark: Short string (5 chars)
----------------------------------------
                           Rate     Shannon::Entropy Shannon::Entropy::XS
Shannon::Entropy       908579/s                   --                 -95%
Shannon::Entropy::XS 18023002/s                1884%                   --

Benchmark: Medium string (43 chars)
----------------------------------------
                          Rate     Shannon::Entropy Shannon::Entropy::XS
Shannon::Entropy      165257/s                   --                 -98%
Shannon::Entropy::XS 8525479/s                5059%                   -- 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Extension: Alternative Approaches to Custom Ops
&lt;/h2&gt;

&lt;p&gt;So far we've covered what I consider the &lt;strong&gt;proper&lt;/strong&gt; way to implement custom ops. But you may encounter other patterns in existing code, and it's worth understanding why some approaches are better than others.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Proper Way (What We Did)
&lt;/h3&gt;

&lt;p&gt;Our call checker restructures the op tree properly:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detaches arguments from the entersub&lt;/li&gt;
&lt;li&gt;Creates a new &lt;code&gt;OP_CUSTOM&lt;/code&gt; unop&lt;/li&gt;
&lt;li&gt;Frees the old entersub entirely
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;newop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newUNOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OP_CUSTOM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;argop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_ppaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pp_entropy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;op_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is more code, but it's correct. The op tree is clean, B::Concise shows your custom op, and it works in all contexts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Quick Hack (Avoid This)
&lt;/h3&gt;

&lt;p&gt;Some other modules take a shortcut - they just swap the &lt;code&gt;op_ppaddr&lt;/code&gt; pointer without restructuring the tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;hack_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;protosv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;protosv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_ppaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_custom_op&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* Return unchanged! */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that your pp function now has to deal with the entersub's stack frame, using TOPMARK/POPMARK manipulation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;my_custom_op&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dSP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;I32&lt;/span&gt; &lt;span class="n"&gt;ax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOPMARK&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;I32&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SP&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;PL_stack_base&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;TOPMARK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;croak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"requires at least 1 argument"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;sv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL_stack_base&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ax&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="cm"&gt;/* ... do work ... */&lt;/span&gt;
    &lt;span class="n"&gt;SP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL_stack_base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;POPMARK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;EXTEND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;PUSHs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newSVnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="cm"&gt;/* Memory leak! */&lt;/span&gt;
    &lt;span class="n"&gt;PUTBACK&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NORMAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why avoid this?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fragile&lt;/strong&gt; - Breaks when your function is called inside other expressions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory leaks&lt;/strong&gt; - Easy to forget mortalization (note &lt;code&gt;newSVnv()&lt;/code&gt; above leaks!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging confusion&lt;/strong&gt; - B::Concise still shows &lt;code&gt;entersub&lt;/code&gt;, not your custom op&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex code&lt;/strong&gt; - The pp function is harder to understand and maintain&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Shared Call Checker (For Multiple Functions)
&lt;/h3&gt;

&lt;p&gt;If you're building a library with many similar functions (like a maths library), you can use a generic call checker that takes a function pointer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;vec_unary_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pp_func&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;pTHX&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;selfop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;pushop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cUNOPx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;OpHAS_SIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;selfop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;selfop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;nextop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selfop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;OpMORESIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;OpLASTSIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selfop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;newop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newUNOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OP_CUSTOM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;selfop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_ppaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pp_func&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;op_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create thin wrappers for each function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;vec_sum_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vec_unary_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aTHX_&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pp_vec_sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;vec_mean_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vec_unary_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aTHX_&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pp_vec_mean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;vec_min_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;vec_unary_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aTHX_&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pp_vec_min&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps your code DRY while still doing proper op tree restructuring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Binary Ops (Two Arguments)
&lt;/h3&gt;

&lt;p&gt;For functions taking two arguments, use &lt;code&gt;newBINOP&lt;/code&gt; instead of &lt;code&gt;newUNOP&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;binary_call_checker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;namegv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;PERL_UNUSED_ARG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;pushop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cUNOPx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;OpHAS_SIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;arg1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;arg2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;nextop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OpSIBLING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;OpMORESIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pushop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nextop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;OpLASTSIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;OpLASTSIB_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="n"&gt;newop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newBINOP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OP_CUSTOM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_ppaddr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pp_my_binary_func&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;op_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entersubop&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pp function for a binary op pops two values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;pp_my_binary_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dSP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;POPs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;POPs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/* combine left and right */&lt;/span&gt;
    &lt;span class="n"&gt;mPUSHn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;RETURN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have any further questions then please do post them below.&lt;/p&gt;

</description>
      <category>perl</category>
      <category>xs</category>
      <category>c</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>LRU::Cache - A Fast Least Recently Used Cache For Perl</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Wed, 08 Apr 2026 12:08:47 +0000</pubDate>
      <link>https://dev.to/lnationorg/lrucache-a-fast-least-recently-used-cache-for-perl-42de</link>
      <guid>https://dev.to/lnationorg/lrucache-a-fast-least-recently-used-cache-for-perl-42de</guid>
      <description>&lt;p&gt;Caching is one of those things every application needs eventually. Whether it's DNS lookups, database rows, or computed results, keeping frequently accessed data close avoids expensive recomputation. The classic data structure for this is the &lt;strong&gt;Least Recently Used (LRU) cache&lt;/strong&gt;: a fixed-size store that automatically evicts the oldest unused entry when full.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LRU::Cache&lt;/code&gt; is a new CPAN module that implements an LRU cache entirely in C via XS. Every operation — &lt;code&gt;get&lt;/code&gt;, &lt;code&gt;set&lt;/code&gt;, &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;exists&lt;/code&gt; — is O(1). No Perl-level hash ties, no linked list objects, no method dispatch on the hot path if you don't want it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;LRU::&lt;/span&gt;&lt;span class="nv"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LRU::Cache::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;user:42&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Alice&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;user:43&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bob&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;   &lt;span class="s"&gt;role&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;editor&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;user:42&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;   &lt;span class="c1"&gt;# promotes to front&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$user&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;                 &lt;span class="c1"&gt;# "Alice"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constructor takes a single argument: the maximum number of entries. Once the cache hits capacity, the next &lt;code&gt;set&lt;/code&gt; evicts whatever was accessed longest ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Eviction Model
&lt;/h2&gt;

&lt;p&gt;Every &lt;code&gt;get&lt;/code&gt; or &lt;code&gt;set&lt;/code&gt; promotes an entry to the front of the list. The entry at the tail — the one untouched for the longest — is the first to go when the cache is full.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LRU::Cache::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;c&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Access 'a' — it moves to the front&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="c1"&gt;# Cache is full. This evicts 'b' (the least recently used).&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;d&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# 'b' is gone&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;   &lt;span class="c1"&gt;# undef&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the &lt;code&gt;get('a')&lt;/code&gt;, the internal order is &lt;code&gt;a → c → b&lt;/code&gt;. Adding &lt;code&gt;d&lt;/code&gt; evicts &lt;code&gt;b&lt;/code&gt; from the tail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Peeking Without Promoting
&lt;/h2&gt;

&lt;p&gt;Sometimes you want to check a value without affecting the eviction order. &lt;code&gt;peek&lt;/code&gt; does exactly that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LRU::Cache::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;c&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# peek returns the value but doesn't promote&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;   &lt;span class="c1"&gt;# 1&lt;/span&gt;

&lt;span class="c1"&gt;# 'a' is still the oldest&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oldest_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;oldest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# 'a'&lt;/span&gt;

&lt;span class="c1"&gt;# Now get('a') promotes it&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oldest_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;oldest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;# 'b'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when you're iterating over cache contents for monitoring or debugging without disturbing the access pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspecting the Cache
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;oldest&lt;/code&gt; and &lt;code&gt;newest&lt;/code&gt; return both the key and value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LRU::Cache::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;first&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;  &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I was first&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;second&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I was second&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;third&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;  &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I was third&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$newest_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$newest_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;newest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;# $newest_key = 'third', $newest_val = 'I was third'&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$oldest_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$oldest_val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;oldest&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;# $oldest_key = 'first', $oldest_val = 'I was first'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;keys&lt;/code&gt; returns all keys in most-recently-used order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;first&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;   &lt;span class="c1"&gt;# promote 'first'&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;# ('first', 'third', 'second')&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;delete&lt;/code&gt; hands back whatever was removed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;second&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="c1"&gt;# $deleted = 'I was second'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Function-Style API
&lt;/h2&gt;

&lt;p&gt;Method dispatch in Perl isn't free. For a cache that sits in a tight loop, the overhead of &lt;code&gt;$cache-&amp;gt;get(...)&lt;/code&gt; — which goes through &lt;code&gt;entersub&lt;/code&gt;, method resolution, and argument shifting — adds up. &lt;code&gt;LRU::Cache&lt;/code&gt; offers a function-style API that is custom OP optimised that eliminates this entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;LRU::&lt;/span&gt;&lt;span class="nv"&gt;Cache&lt;/span&gt; &lt;span class="sx"&gt;qw(import)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LRU::Cache::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;lru_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$v&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;lru_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;lru_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$p&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;lru_peek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;lru_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;key&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I say these aren't just wrappers. At compile time, Perl's call checker mechanism replaces the &lt;code&gt;entersub&lt;/code&gt; op with a custom op that goes directly to the C implementation. No method lookup, no &lt;code&gt;@_&lt;/code&gt; construction, no argument unpacking. The cache pointer is read directly off the pad.&lt;/p&gt;

&lt;p&gt;The result is roughly &lt;strong&gt;2x faster&lt;/strong&gt; than the method-style API, and &lt;strong&gt;5–20x faster&lt;/strong&gt; than a pure Perl implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;Single-operation rates, no batching loops — each iteration is one cache call:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Pure Perl&lt;/th&gt;
&lt;th&gt;XS Method&lt;/th&gt;
&lt;th&gt;XS Function&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;set (existing)&lt;/td&gt;
&lt;td&gt;1,715,893/s&lt;/td&gt;
&lt;td&gt;21,445,932/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45,124,820/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;get (hit)&lt;/td&gt;
&lt;td&gt;5,271,976/s&lt;/td&gt;
&lt;td&gt;18,302,304/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;33,017,565/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;get (miss)&lt;/td&gt;
&lt;td&gt;8,133,594/s&lt;/td&gt;
&lt;td&gt;22,125,453/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;46,998,887/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;exists (hit)&lt;/td&gt;
&lt;td&gt;7,080,776/s&lt;/td&gt;
&lt;td&gt;19,521,882/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;38,745,035/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;peek&lt;/td&gt;
&lt;td&gt;6,410,022/s&lt;/td&gt;
&lt;td&gt;16,842,291/s&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;32,437,007/s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  A Practical Example: Caching Expensive Lookups
&lt;/h2&gt;

&lt;p&gt;Here's a pattern that comes up constantly — wrapping an expensive operation with a cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;LRU::&lt;/span&gt;&lt;span class="nv"&gt;Cache&lt;/span&gt; &lt;span class="sx"&gt;qw(import)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dns_cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;LRU::Cache::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;lru_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dns_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;defined&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;expensive_dns_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;lru_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dns_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$ip&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cache transparently sits in front of the expensive call. Hits are served from C-backed memory in constant time. Misses fall through to the real lookup and get cached for next time. When the cache fills up, stale domains are evicted automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;cpanm&lt;/span&gt; &lt;span class="nn"&gt;LRU::&lt;/span&gt;&lt;span class="nv"&gt;Cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Heap::PQ — A Binary Heap (Priority Queue) Implementation for Perl</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Tue, 07 Apr 2026 07:09:03 +0000</pubDate>
      <link>https://dev.to/lnationorg/heappq-a-binary-heap-priority-queue-implementation-for-perl-p7o</link>
      <guid>https://dev.to/lnationorg/heappq-a-binary-heap-priority-queue-implementation-for-perl-p7o</guid>
      <description>&lt;h2&gt;
  
  
  So What Is A Binary Heap?
&lt;/h2&gt;

&lt;p&gt;A binary heap is really just an sorted array pretending to be a tree. Each element has a parent and children, but instead of pointers you find them with simple array indexes. The trick is that every parent follows one rule relative to its children, and that rule decides what kind of heap you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Min heap&lt;/strong&gt; - every parent is less than or equal to its children. The smallest element always lives at the root/top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max heap&lt;/strong&gt; - every parent is greater than or equal to its children. The largest element always lives at the root/top.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the tree is complete, the parent of element at index &lt;em&gt;i&lt;/em&gt; sits at index &lt;em&gt;floor((i−1)/2)&lt;/em&gt;, and its children sit at &lt;em&gt;2i+1&lt;/em&gt; and &lt;em&gt;2i+2&lt;/em&gt;. No pointers, no linked lists — just arithmetic on array indices. That cache friendly layout is one reason heaps are so fast in practice.&lt;/p&gt;

&lt;p&gt;The key operations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;push&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;td&gt;Insert a value, then sift it up to restore order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pop&lt;/td&gt;
&lt;td&gt;O(log n)&lt;/td&gt;
&lt;td&gt;Remove the root, move the last element up, sift down&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;peek&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;Read the root without removing it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;make_heap&lt;/td&gt;
&lt;td&gt;O(n)&lt;/td&gt;
&lt;td&gt;Turn an existing unsorted array into a valid heap (&lt;code&gt;make_heap_min&lt;/code&gt; / &lt;code&gt;make_heap_max&lt;/code&gt;, &lt;code&gt;new('min')&lt;/code&gt;, &lt;code&gt;new('max')&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This makes a heap the natural backbone of a &lt;strong&gt;priority queue&lt;/strong&gt; — a collection where you always want the next most important item and you need insertions to stay fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Heap::PQ?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Heap::PQ&lt;/strong&gt; brings binary heaps to Perl as a C extension. Where a pure Perl heap tops out around 300 operations per second, Heap::PQ's functional interface clocks over 11,000 when pushing a 1000 random integers — and if you are just handling plain integers then there is an optimised NV heap implementation that squeezes out 50% more performance. It ships with three interfaces (object oriented, functional custom ops or a raw array) so you can trade readability for throughput depending on what your code needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cpanm Heap::PQ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Create a heap, push values in, and they come back out in priority order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Heap::&lt;/span&gt;&lt;span class="nv"&gt;PQ&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$pq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;peek&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;  &lt;span class="c1"&gt;# 1 — the smallest value is always at the root&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;is_empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;  &lt;span class="c1"&gt;# 1, 3, 5&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Switch to &lt;code&gt;'max'&lt;/code&gt; and the largest element comes out first instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Ways to Use It
&lt;/h2&gt;

&lt;h3&gt;
  
  
  OO Interface
&lt;/h3&gt;

&lt;p&gt;The object oriented API is the clearest to read if you're a traditional perl developer that likes OO. &lt;code&gt;push&lt;/code&gt; returns the heap so calls can be chained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;push_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;# 2&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Functional Interface
&lt;/h3&gt;

&lt;p&gt;Import with &lt;code&gt;'import'&lt;/code&gt; and Heap::PQ installs &lt;code&gt;heap_push&lt;/code&gt;, &lt;code&gt;heap_pop&lt;/code&gt;, &lt;code&gt;heap_peek&lt;/code&gt;, &lt;code&gt;heap_size&lt;/code&gt;, and &lt;code&gt;heap_is_empty&lt;/code&gt; as custom ops. Perl compiles them down to optimised opcodes so there is no method dispatch overhead at runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Heap::&lt;/span&gt;&lt;span class="nv"&gt;PQ&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;heap_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;heap_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$val&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;heap_pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;# 7&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;heap_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$top&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;heap_peek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;# 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the fastest path as stated functional custom ops removes the method&lt;code&gt;-&amp;gt;&lt;/code&gt;dispatch overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Raw Array Interface
&lt;/h3&gt;

&lt;p&gt;You can also operate directly on a Perl array. Import with &lt;code&gt;'raw'&lt;/code&gt; to get &lt;code&gt;make_heap_min&lt;/code&gt;, &lt;code&gt;push_heap_min&lt;/code&gt;, &lt;code&gt;pop_heap_min&lt;/code&gt; (and their &lt;code&gt;_max&lt;/code&gt; counterparts). This skips the object entirely but the functional interface is still faster thanks to custom op optimisation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Heap::&lt;/span&gt;&lt;span class="nv"&gt;PQ&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;make_heap_min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;@data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;# O(n) heapify in place&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$min&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;pop_heap_min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;@data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;push_heap_min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;@data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No object, no opaque struct — just an array whose layout satisfies the heap property. Useful when you already have data in an array and want to avoid copying it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Comparators
&lt;/h2&gt;

&lt;p&gt;By default the heap compares values numerically. Pass a code reference as a second argument to &lt;code&gt;new&lt;/code&gt; and you can heap order anything — hash references, objects, strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;due&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;due&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Write tests&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;  &lt;span class="s"&gt;due&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ship release&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;due&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fix bug&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;      &lt;span class="s"&gt;due&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;is_empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$t&lt;/span&gt;&lt;span class="s2"&gt;-&amp;gt;{name}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Fix bug&lt;/span&gt;
&lt;span class="c1"&gt;# Write tests&lt;/span&gt;
&lt;span class="c1"&gt;# Ship release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comparator receives two elements in &lt;code&gt;$a&lt;/code&gt; and &lt;code&gt;$b&lt;/code&gt; — exactly like Perl's &lt;code&gt;sort&lt;/code&gt; — and should return a negative, zero, or positive value. String ordering works the same way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="ow"&gt;cmp&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$alpha&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;push_all&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;banana&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apple&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cherry&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$alpha&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;  &lt;span class="c1"&gt;# apple&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Path Comparators
&lt;/h2&gt;

&lt;p&gt;When your heap contains hash references and you just want to compare by a numeric field, pass the field name as a string instead of a code reference. The comparison happens entirely in C — no Perl callback overhead at all:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Write tests&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;  &lt;span class="s"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ship release&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fix bug&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;      &lt;span class="s"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;is_empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$tasks&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$t&lt;/span&gt;&lt;span class="s2"&gt;-&amp;gt;{name}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# Fix bug&lt;/span&gt;
&lt;span class="c1"&gt;# Write tests&lt;/span&gt;
&lt;span class="c1"&gt;# Ship release&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dot separated paths reach into nested hashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meta.score&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$heap&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;  &lt;span class="c1"&gt;# 'b'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is extracted once when you &lt;code&gt;push&lt;/code&gt;, so sift operations run at the same speed as a plain numeric heap. In benchmarks, key path heaps match the no-comparator path (~6,400/s) while custom Perl comparators top out around ~1,200/s — a 5× difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  NV Heaps — Native Doubles, No SV Overhead
&lt;/h2&gt;

&lt;p&gt;When every value is a number, &lt;code&gt;new_nv&lt;/code&gt; stores them as raw C doubles instead of Perl scalars. That eliminates SV allocation and reference counting entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new_nv&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;push_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;3.14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.71&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.73&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;  &lt;span class="c1"&gt;# 1.41&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In benchmarks the NV heap runs roughly 1.5× faster than the functional interface for push heavy workloads, and nearly 57× faster than pure Perl.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching and Deleting
&lt;/h2&gt;

&lt;p&gt;Both &lt;code&gt;search&lt;/code&gt; and &lt;code&gt;delete&lt;/code&gt; accept a callback that receives each element in &lt;code&gt;$_&lt;/code&gt;. &lt;code&gt;search&lt;/code&gt; returns matching elements; &lt;code&gt;delete&lt;/code&gt; removes them and returns how many were dropped:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$pq&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;push_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Find all elements greater than 12&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@big&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;# @big = (15, 20, 25)&lt;/span&gt;

&lt;span class="c1"&gt;# Remove them from the heap&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$removed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pq&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;# $removed = 3, heap now contains (1, 5, 10)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a delete, Heap::PQ rebuilds the heap in O(n) with Floyd's algorithm so the ordering invariant is always intact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Peeking at the Top N
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;peek_n&lt;/code&gt; returns the top N elements in sorted order without removing them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$scores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;max&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$scores&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;push_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@top3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$scores&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;peek_n&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;# (99, 95, 91)&lt;/span&gt;
&lt;span class="c1"&gt;# Heap is unchanged — still has all 6 elements&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;peek_n&lt;/code&gt; works with the heap's built-in numeric comparison, so it is best suited to plain numeric heaps. For heaps that use a custom comparator, pop and re-push to inspect the top N.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together: An Event Scheduler
&lt;/h2&gt;

&lt;p&gt;A priority queue is a natural fit for an event loop — push events with a timestamp, pop them in chronological order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Heap::&lt;/span&gt;&lt;span class="nv"&gt;PQ&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$scheduler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Heap::PQ::&lt;/span&gt;&lt;span class="nv"&gt;new&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;min&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;time&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt; &lt;span class="c1"&gt;# compare by the 'time' key in C&lt;/span&gt;

&lt;span class="nv"&gt;heap_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1712500800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start_server&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;heap_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1712497200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;load_config&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;heap_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1712504400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;open_connections&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;heap_push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scheduler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1712502600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warm_cache&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;heap_is_empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scheduler&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;heap_pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$scheduler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;t=%d  %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# t=1712497200  load_config&lt;/span&gt;
&lt;span class="c1"&gt;# t=1712500800  start_server&lt;/span&gt;
&lt;span class="c1"&gt;# t=1712502600  warm_cache&lt;/span&gt;
&lt;span class="c1"&gt;# t=1712504400  open_connections&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;All numbers below are from pushing 1,000 random integers then popping them all, measured with &lt;code&gt;Benchmark&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;vs Pure Perl&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pure Perl&lt;/td&gt;
&lt;td&gt;305/s&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom comparator&lt;/td&gt;
&lt;td&gt;1,194/s&lt;/td&gt;
&lt;td&gt;4x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array::Heap&lt;/td&gt;
&lt;td&gt;6,087/s&lt;/td&gt;
&lt;td&gt;20x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap::PQ OO key path&lt;/td&gt;
&lt;td&gt;6,359/s&lt;/td&gt;
&lt;td&gt;21x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap::PQ OO&lt;/td&gt;
&lt;td&gt;6,685/s&lt;/td&gt;
&lt;td&gt;22x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap::PQ raw&lt;/td&gt;
&lt;td&gt;8,665/s&lt;/td&gt;
&lt;td&gt;28x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap::PQ func&lt;/td&gt;
&lt;td&gt;11,416/s&lt;/td&gt;
&lt;td&gt;37x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap::PQ NV&lt;/td&gt;
&lt;td&gt;16,747/s&lt;/td&gt;
&lt;td&gt;55x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The custom op overhead reduction is clearly visible in the functional row, and the NV heap's custom op implementation plus avoidance of SV allocation pushes throughput higher still.&lt;/p&gt;

&lt;p&gt;If you have any questions please do comment.&lt;/p&gt;

&lt;p&gt;A Side note: I'm currently on look out for a new contract or permanent opportunity. If you know of any relevant openings, I'd appreciate hearing from you - &lt;a href="mailto:email@lnation.org"&gt;email@lnation.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Chandra: Cross-Platform Desktop GUIs in Perl</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sat, 04 Apr 2026 18:53:20 +0000</pubDate>
      <link>https://dev.to/lnationorg/chandra-cross-platform-desktop-guis-in-perl-1ah2</link>
      <guid>https://dev.to/lnationorg/chandra-cross-platform-desktop-guis-in-perl-1ah2</guid>
      <description>&lt;p&gt;Building desktop applications has traditionally been a challenge in the Perl ecosystem. While we have excellent tools for web development, backend services, and system administration, creating native feeling desktop apps often meant learning and wrestling with complex GUI toolkits or abandoning Perl altogether.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chandra&lt;/strong&gt; attempts to change that equation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfn8p3lg5skrtd7jsxzm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfn8p3lg5skrtd7jsxzm.png" alt="Chat" width="800" height="551"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Chandra?
&lt;/h2&gt;

&lt;p&gt;Chandra is a Perl module that lets you build cross-platform desktop applications using the web technologies you already know — HTML, CSS, and JavaScript — while keeping your application logic in Perl. Under the hood, it leverages native webview components (WebKit on macOS/Linux, Edge on Windows) to render your UI, giving you the best of both worlds: native performance with web flexibility.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;App&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;App&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;title&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My First App&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;width&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set_content&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;Hello from Perl!&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Five lines to create a windowed application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyqhp36v2iszkzh5d14o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyqhp36v2iszkzh5d14o.png" alt="Hello World" width="592" height="332"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bridge Between Worlds
&lt;/h2&gt;

&lt;p&gt;The real power of Chandra lies in its seamless bidirectional communication between Perl and JavaScript. You can expose Perl functions to your frontend with a simple &lt;code&gt;bind&lt;/code&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;App&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HTML&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;!&lt;/span&gt;&lt;span class="nv"&gt;DOCTYPE&lt;/span&gt; &lt;span class="nv"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;h1&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;button&lt;/span&gt; &lt;span class="nv"&gt;onclick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inc()&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;+&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;button&lt;/span&gt; &lt;span class="nv"&gt;onclick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dec()&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;-&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nv"&gt;async&lt;/span&gt; &lt;span class="nv"&gt;function&lt;/span&gt; &lt;span class="nv"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;await&lt;/span&gt; &lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;chandra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
            &lt;span class="nv"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="sr"&gt;//&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt;
        &lt;span class="nv"&gt;function&lt;/span&gt; &lt;span class="nv"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;window&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;chandra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;decrement&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nv"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;val&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;script&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;html&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;HTML&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your JavaScript calls &lt;code&gt;window.chandra.invoke('increment')&lt;/code&gt;, Perl increments the counter, and the result flows back as a Promise. JSON serialization happens automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1js3a3kzzkbh1kqg1ijk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1js3a3kzzkbh1kqg1ijk.png" alt="Increment" width="794" height="700"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building UIs the Perl Way
&lt;/h2&gt;

&lt;p&gt;If you prefer constructing your UI in Perl rather than writing raw HTML strings, Chandra::Element provides a DOM-like interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;Element&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;Element&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;container&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;style&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;background&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;tag&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
            &lt;span class="s"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Click Me&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
            &lt;span class="s"&gt;onclick&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Button clicked!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ui&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Event handlers are automatically wired up — when the button is clicked, your Perl callback fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  Single-Page Apps with Built-in Routing
&lt;/h2&gt;

&lt;p&gt;Modern desktop apps often benefit from SPA-style navigation. Chandra has you covered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;App&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My SPA&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sx"&gt;qq{
        &amp;lt;nav&amp;gt;
            &amp;lt;a href="/"&amp;gt;Home&amp;lt;/a&amp;gt;
            &amp;lt;a href="/settings"&amp;gt;Settings&amp;lt;/a&amp;gt;
        &amp;lt;/nav&amp;gt;
        &amp;lt;div id="chandra-content"&amp;gt;$body&amp;lt;/div&amp;gt;
    }&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;route&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Welcome!&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;route&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;/settings&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;h1&amp;gt;Settings&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Configure your app...&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;route&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;/user/:id&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;%params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;h1&amp;gt;User &lt;/span&gt;&lt;span class="si"&gt;$params&lt;/span&gt;&lt;span class="s2"&gt;{id}&amp;lt;/h1&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigation happens client-side with no page reloads — just like a web SPA, but in a native window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Integration Done Right
&lt;/h2&gt;

&lt;p&gt;Chandra doesn't just wrap a browser. It provides genuine desktop integration:&lt;/p&gt;

&lt;h3&gt;
  
  
  Native Dialogs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# File picker&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;dialog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;open_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Select a file&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="c1"&gt;# Directory picker  &lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;dialog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;open_directory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Save dialog&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$save_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;dialog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;save_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;title&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Save As&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;document.txt&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Alert dialogs&lt;/span&gt;
&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;dialog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Operation complete!&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;dialog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Something went wrong&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1kf4wkd6s3x91mklvx1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1kf4wkd6s3x91mklvx1.png" alt="Dialog" width="800" height="688"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  System Tray
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;Tray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$tray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;Tray&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;app&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;icon&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;icon.png&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;tooltip&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;My App&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$tray&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Show Window&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;show&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$tray&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add_separator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$tray&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Quit&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;terminate&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nv"&gt;$tray&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;show&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5jnsysvzuempfkbvacc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5jnsysvzuempfkbvacc.png" alt="Tray" width="246" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistent Storage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;store&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# ~/.chandra/&amp;lt;app-name&amp;gt;/store.json&lt;/span&gt;

&lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;window.width&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;1200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;recent_files&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;['&lt;/span&gt;&lt;span class="s1"&gt;/path/a&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/path/b&lt;/span&gt;&lt;span class="p"&gt;']);&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$theme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;get&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;  &lt;span class="c1"&gt;# 'dark'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dot notation for nested keys, automatic JSON serialization, atomic writes with file locking — storage that just works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hot Reload
&lt;/h3&gt;

&lt;p&gt;Wtch your source files and refresh automatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;lib/&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Files changed: @&lt;/span&gt;&lt;span class="si"&gt;$changed&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
    &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;build_ui&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nv"&gt;$app&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;run&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesfhfavseu7cof0yhq4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fesfhfavseu7cof0yhq4c.png" alt="Reload" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  DevTools
&lt;/h3&gt;

&lt;p&gt;Need to inspect elements or debug JavaScript? Enable developer tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Chandra::&lt;/span&gt;&lt;span class="nv"&gt;App&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Debug App&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;debug&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Press F12 to open DevTools&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Platform Support
&lt;/h2&gt;

&lt;p&gt;Chandra works across the major desktop platforms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt; — Uses WebKit via WKWebView&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt; — Uses WebKitGTK&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt; — Uses MSHTML/Edge&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Install from CPAN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cpanm Chandra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or from source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;perl Makefile.PL
make
make &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then dive into the examples directory for working demonstrations of all major features or I also have several modules already available for you to test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Chandra::EPUB" rel="noopener noreferrer"&gt;Chandra::EPUB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj53p1hnre6hkxua7uga7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj53p1hnre6hkxua7uga7.png" alt="Van Goph" width="800" height="981"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Ascii::Text::Chandra" rel="noopener noreferrer"&gt;Ascii::Text::Chandra&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4md1nfwuis2j548o7d90.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4md1nfwuis2j548o7d90.png" alt=" " width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://metacpan.org/pod/Chandra::Game::Tetris" rel="noopener noreferrer"&gt;Chandra::Game::Tetris&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qi5z9vobvi8in4k5auc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3qi5z9vobvi8in4k5auc.png" alt="Tetris" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Side note: I'm currently on look out for a new contract or permanent opportunity. If you know of any relevant openings, I'd appreciate hearing from you - &lt;a href="mailto:email@lnation.org"&gt;email@lnation.org&lt;/a&gt;&lt;/p&gt;

</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Introducing Object::Proto — A Prototype Object System for Perl</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Fri, 03 Apr 2026 11:54:34 +0000</pubDate>
      <link>https://dev.to/lnationorg/introducing-objectproto-a-prototype-object-system-for-perl-327j</link>
      <guid>https://dev.to/lnationorg/introducing-objectproto-a-prototype-object-system-for-perl-327j</guid>
      <description>&lt;p&gt;Perl's object system is famously flexible. &lt;code&gt;bless&lt;/code&gt; a reference, add some accessors, and you're in business. Over the years, modules like Moose, Moo, and Mouse have built increasingly sophisticated layers on top of that foundation — giving us type constraints, roles, lazy attributes, and declarative syntax.&lt;/p&gt;

&lt;p&gt;But they all share the same bedrock: &lt;strong&gt;a blessed hash reference&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Object::Proto&lt;/code&gt; takes a less used approach. It stores objects as &lt;strong&gt;blessed arrays&lt;/strong&gt;, maps property names to slot indices at compile time, and compiles accessors down to &lt;strong&gt;custom ops&lt;/strong&gt; — the same mechanism Perl uses internally for built-in operations. The result is an object system that is type-safe, feature-rich, and significantly faster than the previously mentioned alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Basics
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::&lt;/span&gt;&lt;span class="nv"&gt;Proto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name:Str:required&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sound:Str:default(silence)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;age:Int&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Animal&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Whiskers&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;# Whiskers&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;# silence&lt;/span&gt;
&lt;span class="nv"&gt;$cat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;# setter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;object&lt;/code&gt; keyword defines a class at compile time. Property specifications use a colon-separated format: &lt;code&gt;name:Type:modifier&lt;/code&gt;. No hash lookups at runtime — &lt;code&gt;$cat-&amp;gt;name&lt;/code&gt; compiles to a direct array slot access.&lt;/p&gt;

&lt;p&gt;Both positional and named constructors are supported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Animal&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Whiskers&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;meow&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;# positional (fastest)&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Animal&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Whiskers&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;# named pairs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What You Get
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Built-in Types (Zero Overhead)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Any  Defined  Str  Int  Num  Bool  ArrayRef  HashRef  CodeRef  Object
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are checked inline at the C level — no function call, no callback. You can also register custom types in Perl or in XS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slot Modifiers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name:Str:required&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email:Str:required:readonly&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;age:Int:default(0)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nickname:Str:clearer:predicate&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;settings:HashRef:lazy:builder(_build_settings)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;parent:Object:weak&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;internal_id:Int:arg(_id)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;       &lt;span class="c1"&gt;# constructor uses _id, accessor uses internal_id&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;score:Num:trigger(on_score_change)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rank:Str:reader(get_rank):writer(set_rank)&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything you'd expect from a modern object system: &lt;code&gt;required&lt;/code&gt;, &lt;code&gt;readonly&lt;/code&gt;, &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;lazy&lt;/code&gt;, &lt;code&gt;builder&lt;/code&gt;, &lt;code&gt;clearer&lt;/code&gt;, &lt;code&gt;predicate&lt;/code&gt;, &lt;code&gt;reader&lt;/code&gt;, &lt;code&gt;writer&lt;/code&gt;, &lt;code&gt;trigger&lt;/code&gt;, &lt;code&gt;weak&lt;/code&gt; references, and &lt;code&gt;arg&lt;/code&gt; (init_arg) — all compiled to C/XS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inheritance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name:Str:required&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sound:Str&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;extends&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;breed:Str&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Dog&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rex&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Woof&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;breed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lab&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$dog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;isa&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;  &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple inheritance and multi-level chains work as youl would expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Triathlete&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;extends&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;['&lt;/span&gt;&lt;span class="s1"&gt;Swimmer&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;'],&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;event:Str&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important note:&lt;/strong&gt; because &lt;code&gt;Object::Proto&lt;/code&gt; objects are arrays and Moo/Moose/Mouse objects are hashes, you &lt;strong&gt;cannot inherit from Moo, Moose, or Mouse classes&lt;/strong&gt;  (or vice versa). The internal representations are fundamentally incompatible. If you need to interoperate, use composition (roles or delegation via slots) rather than inheritance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Roles
&lt;/h3&gt;

&lt;p&gt;Roles work the way you'd expect — define a bundle of slots and method requirements, then compose them into any class. A role can carry its own attributes and demand that consuming classes implement specific methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::&lt;/span&gt;&lt;span class="nv"&gt;Proto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;role&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Printable&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;format:Str:default(text)&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;requires&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Printable&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title:Str:required&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body:Str&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Printable&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Document&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;title&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;: &lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;body&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;format&lt;/code&gt; slot from &lt;code&gt;Printable&lt;/code&gt; is merged into &lt;code&gt;Document&lt;/code&gt; at define time. If &lt;code&gt;Document&lt;/code&gt; didn't provide a &lt;code&gt;to_string&lt;/code&gt; method, the &lt;code&gt;with&lt;/code&gt; call would croak. You can check role consumption at runtime with &lt;code&gt;Object::Proto::does($obj, 'Printable')&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method Modifiers
&lt;/h3&gt;

&lt;p&gt;You can wrap existing methods without subclassing. &lt;code&gt;before&lt;/code&gt; runs code ahead of the original, &lt;code&gt;after&lt;/code&gt; runs code after it, and &lt;code&gt;around&lt;/code&gt; gives you full control — you receive the original method as &lt;code&gt;$orig&lt;/code&gt; and decide whether (and how) to call it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;before&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;About to bark...&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nv"&gt;after&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Done barking.&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nv"&gt;around&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bark&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$orig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;@args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;uc&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$orig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiple modifiers can be stacked on the same method. They're stored as linked lists in C, so the dispatch overhead is minimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prototype Chains
&lt;/h3&gt;

&lt;p&gt;This is where &lt;code&gt;Object::Proto&lt;/code&gt; gets its name. Like JavaScript's prototype model, any object can delegate to another object. When you access a property that is &lt;code&gt;undef&lt;/code&gt; in the current object, the lookup walks up the prototype chain until it finds a defined value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$defaults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Animal&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Default&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;silence&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Animal&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Whiskers&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="nv"&gt;$cat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;set_prototype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$defaults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;sound&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# 'silence' — resolved from prototype&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;$cat&lt;/code&gt; has no &lt;code&gt;sound&lt;/code&gt; of its own, so the accessor walks up to &lt;code&gt;$defaults&lt;/code&gt; and returns &lt;code&gt;'silence'&lt;/code&gt;. Setting &lt;code&gt;$cat-&amp;gt;sound('meow')&lt;/code&gt; writes to &lt;code&gt;$cat&lt;/code&gt; directly — prototypes are never mutated by child writes. You can inspect the full chain with &lt;code&gt;Object::Proto::prototype_chain($obj)&lt;/code&gt; and measure its depth with &lt;code&gt;Object::Proto::prototype_depth($obj)&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mutability Controls
&lt;/h3&gt;

&lt;p&gt;Objects can be locked (preventing structural changes) or frozen (permanent deep immutability). A frozen object's setters will &lt;code&gt;croak&lt;/code&gt; on any attempt to modify — useful for configuration objects, default prototypes, or any value you want to guarantee stays constant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;# prevent structural changes&lt;/span&gt;
&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;unlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;freeze&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;# permanent immutability — setters will croak&lt;/span&gt;
&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;is_frozen&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locking is reversible; freezing is not. The frozen check is implemented as a single bitflag test in C — it adds no measurable overhead to the normal (unfrozen) code path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloning &amp;amp; Introspection
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Object::Proto&lt;/code&gt; provides a full introspection API. You can clone objects (the clone is always fresh, unfrozen and unlocked, even if the original wasn't), list properties, inspect slot metadata, and walk the inheritance tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;# shallow copy, unfrozen&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$info&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;slot_info&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;ancestors&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;slot_info&lt;/code&gt; returns a hashref with everything about a slot: its type, whether it's required, readonly, lazy, has a default, trigger, builder, and so on. Useful for building serializers, form generators, or debugging tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Singletons
&lt;/h3&gt;

&lt;p&gt;Need a class that only ever has one instance? Mark it as a singleton and use &lt;code&gt;-&amp;gt;instance()&lt;/code&gt; instead of &lt;code&gt;new&lt;/code&gt;. The first call constructs the object; every subsequent call returns the same one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;db_host:Str&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;db_port:Int&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;singleton&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;db_host&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;db_port&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;# Config-&amp;gt;instance returns the same object every time after first call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is handled at the C level — no Perl-side &lt;code&gt;state&lt;/code&gt; variable or &lt;code&gt;CHECK&lt;/code&gt; block trickery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Function-Style Accessors
&lt;/h2&gt;

&lt;p&gt;For the absolute fastest access, &lt;code&gt;Object::Proto&lt;/code&gt; offers function-style accessors that bypass method dispatch entirely. These are compiled to custom ops at compile time — a direct array slot read/write with no &lt;code&gt;entersub&lt;/code&gt;, no method resolution, no hash lookup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::&lt;/span&gt;&lt;span class="nv"&gt;Proto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x:Num&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;y:Num&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;import_accessors&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Point&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;# 1.0 — custom op, not a method call&lt;/span&gt;
&lt;span class="sr"&gt;y($p, 3.0)&lt;/span&gt;&lt;span class="err"&gt;;          # set y to 3.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Here are benchmarks from a mixed &lt;code&gt;new&lt;/code&gt; + &lt;code&gt;set&lt;/code&gt; + &lt;code&gt;get&lt;/code&gt; workload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       Rate    Pure Hash  Pure Array    XS OO   Typed  Raw Hash Ref  XS func  Raw Hash
Pure Hash         1824921/s           --       -14%     -48%     -64%       -66%       -66%     -70%
Pure Array        2124357/s          16%         --     -40%     -58%       -60%       -61%     -65%
Object::Proto OO  3541833/s          94%        67%       --     -31%       -34%       -35%     -41%
Object::Proto T   5114883/s         180%       141%      44%       --        -4%        -6%     -15%
Raw Hash Ref      5331773/s         192%       151%      51%       4%         --        -2%     -12%
Object::Proto fn  5427162/s         197%       156%      53%       6%        2%          --     -10%
Raw Hash          6024884/s         230%       184%      70%      18%       13%        11%       --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function-style path (&lt;code&gt;XS func&lt;/code&gt;) runs &lt;strong&gt;slightly faster than raw hash reference access&lt;/strong&gt; — while giving you a real object with type constraints, constructors, and a class hierarchy. The method-style path (&lt;code&gt;XS OO&lt;/code&gt;) is nearly &lt;strong&gt;2x faster&lt;/strong&gt; than a pure-Perl hash-based object.&lt;/p&gt;

&lt;p&gt;For hot &lt;code&gt;set&lt;/code&gt; + &lt;code&gt;get&lt;/code&gt; loops on pre-constructed objects, the XS function path has been measured at over &lt;strong&gt;32 million operations per second&lt;/strong&gt; — faster than a hash &lt;code&gt;$hash{key}&lt;/code&gt; access.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the Type System from XS
&lt;/h2&gt;

&lt;p&gt;If you write XS modules, you can register types with C-level check functions that run at near-zero cost (~5 CPU cycles vs ~100 for Perl callbacks):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* In your BOOT section */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"object_types.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;check_positive_int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SvIOK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;BOOT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;object_register_type_xs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aTHX_&lt;/span&gt; &lt;span class="s"&gt;"PositiveInt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check_positive_int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;MyTypes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# registers in BOOT&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::&lt;/span&gt;&lt;span class="nv"&gt;Proto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;object&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value:PositiveInt:required&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  For Moo/Moose Users: Object::Proto::Sugar
&lt;/h2&gt;

&lt;p&gt;If you prefer declarative Moo-style syntax, &lt;code&gt;Object::Proto::Sugar&lt;/code&gt; wraps the XS layer with familiar keywords. Everything compiles down to the same custom ops underneath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;Sugar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;isa&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="s"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;isa&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;silence&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="s"&gt;age&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;isa&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;speak&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;sound&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;Sugar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;extends&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="s"&gt;breed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;isa&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Str&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Dog&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Rex&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;sound&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Woof&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;breed&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lab&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$dog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;speak&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;# Woof&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$dog&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;isa&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt; &lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every feature from the core module is available through the Sugar interface: &lt;code&gt;lazy&lt;/code&gt;, &lt;code&gt;builder&lt;/code&gt;, &lt;code&gt;coerce&lt;/code&gt;, &lt;code&gt;trigger&lt;/code&gt;, &lt;code&gt;predicate&lt;/code&gt;, &lt;code&gt;clearer&lt;/code&gt;, &lt;code&gt;reader&lt;/code&gt;, &lt;code&gt;writer&lt;/code&gt;, &lt;code&gt;weak_ref&lt;/code&gt;, &lt;code&gt;init_arg&lt;/code&gt;, roles (&lt;code&gt;use Object::Proto::Sugar -role&lt;/code&gt; / &lt;code&gt;with&lt;/code&gt; / &lt;code&gt;requires&lt;/code&gt;), method modifiers (&lt;code&gt;before&lt;/code&gt;, &lt;code&gt;after&lt;/code&gt;, &lt;code&gt;around&lt;/code&gt;), and function-style accessors via &lt;code&gt;accessor =&amp;gt; 1&lt;/code&gt;. The Sugar layer runs at compile time via &lt;code&gt;BEGIN::Lift&lt;/code&gt; and then &lt;code&gt;Devel::Hook&lt;/code&gt; via &lt;code&gt;BIGIN&lt;/code&gt; and &lt;code&gt;UNITCHECK&lt;/code&gt; hooks — by the time your first line of runtime code executes, everything has been compiled down to the same custom ops as the raw &lt;code&gt;object&lt;/code&gt; keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sugar with Function-Style Accessors
&lt;/h3&gt;

&lt;p&gt;Add &lt;code&gt;accessor =&amp;gt; 1&lt;/code&gt; to a &lt;code&gt;has&lt;/code&gt; declaration to get a function style accessor alongside the method style one. Then call &lt;code&gt;import_accessors&lt;/code&gt; in your package so the functions are visible when your calling code compiles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Object::Proto::&lt;/span&gt;&lt;span class="nv"&gt;Sugar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="s"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rw&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;isa&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;accessor&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;has&lt;/span&gt; &lt;span class="sr"&gt;y =&amp;gt; ( is =&amp;gt; 'rw', isa =&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;accessor&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;Point&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;import_accessors&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;# install before compilation&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nv"&gt;Point&lt;/span&gt; &lt;span class="s"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;# 1.0 — compiled to custom op&lt;/span&gt;
&lt;span class="sr"&gt;y($p, 3.0)&lt;/span&gt;&lt;span class="err"&gt;;        # direct array write
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sugar Benchmarks vs Moo and Mouse
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       Rate       Moo     Mouse   Sugar   Sugar (fn)
Moo               1264320/s        --       -2%    -55%       -60%
Mouse             1290237/s        2%        --    -54%       -59%
Sugar (method)    2784378/s      120%      116%      --       -12%
Sugar (func)      3174093/s      151%      146%     14%         --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sugar method-style is &lt;strong&gt;2.2x Moo&lt;/strong&gt;. Sugar function-style is &lt;strong&gt;2.5x Moo&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Note on Compatibility
&lt;/h3&gt;

&lt;p&gt;Once again a reminder: because &lt;code&gt;Object::Proto&lt;/code&gt; objects are arrays and Moo/Moose/Mouse objects are hashes, &lt;strong&gt;you cannot inherit across the boundary&lt;/strong&gt;. &lt;code&gt;extends 'My::Moo::Class'&lt;/code&gt; will not work. Use role or delegation to bridge the two worlds if needed. Within its own ecosystem — &lt;code&gt;Object::Proto&lt;/code&gt; classes inheriting from other &lt;code&gt;Object::Proto&lt;/code&gt; classes — everything works seamlessly, including multiple inheritance.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Object::Proto&lt;/code&gt; is available on CPAN and licensed under the Artistic License 2.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cpanm Object::Proto
cpanm Object::Proto::Sugar   &lt;span class="c"&gt;# optional, for Moo-like syntax&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://metacpan.org/pod/Object::Proto" rel="noopener noreferrer"&gt;MetaCPAN&lt;/a&gt;&lt;/p&gt;

</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Loo: Yet Another Way To Introspect Data</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Wed, 01 Apr 2026 07:48:49 +0000</pubDate>
      <link>https://dev.to/lnationorg/loo-yet-another-way-to-introspect-data-2fib</link>
      <guid>https://dev.to/lnationorg/loo-yet-another-way-to-introspect-data-2fib</guid>
      <description>&lt;p&gt;&lt;strong&gt;A Side note:&lt;/strong&gt; I'm currently on look out for a new contract or permanent opportunity. If you know of any relevant openings, I'd appreciate hearing from you. I am a proficient front-end developer also - &lt;a href="mailto:email@lnation.org"&gt;email@lnation.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've spent any time debugging Perl, you've used &lt;code&gt;Data::Dumper&lt;/code&gt;. It's one of those modules that ships with every Perl installation, gets loaded into every debugging session, and does its job without complaint. But it also hasn't changed much in a long time. The output is monochrome, the internals are pure Perl, and code references remain opaque &lt;code&gt;sub { "DUMMY" }&lt;/code&gt; blobs unless you enable &lt;code&gt;Deparse&lt;/code&gt; and even then you do not get the response one would expect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loo&lt;/strong&gt; is a new take on the same problem: dump Perl data structures to readable output, but do it in C, with colour, and with a built-in code deparser that walks the op tree directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Another Dumper?
&lt;/h2&gt;

&lt;p&gt;Three reasons drove the creation of Loo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Speed.&lt;/strong&gt; Loo is implemented entirely in XS. The dump logic, string escaping, colour code generation, and op tree walking all happen in C. For large or deeply nested structures, this matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colour out of the box.&lt;/strong&gt; When your terminal supports it, Loo's output is coloured by default. Strings, numbers, hash keys, braces, blessed class names, regex patterns, and code all get distinct colours that can be customised. There's no separate module to install, no formatter to configure. It auto-detects terminal capability, respects &lt;code&gt;$ENV{NO_COLOR}&lt;/code&gt;, and falls back to plain text when appropriate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code deparsing without B::Deparse.&lt;/strong&gt; When you pass a code reference to Loo with deparsing enabled, it walks Perl's internal op tree in C and reconstructs the source. This is not a wrapper around &lt;code&gt;B::Deparse&lt;/code&gt; — it's a standalone implementation that lives in the same XS compilation unit as the introspecter itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Loo provides a functional interface that mirrors &lt;code&gt;Data::Dumper&lt;/code&gt; closely enough that switching is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Loo&lt;/span&gt; &lt;span class="sx"&gt;qw(Dump cDump ncDump dDump)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Colour auto-detected based on terminal&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;Dump&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Perl&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;5.40&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;# Force colour on (useful when piping to a pager that supports ANSI)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;cDump&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;# Force colour off (useful for logging or file output)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;ncDump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;%ENV&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Dump with code deparsing enabled&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;dDump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OO interface supports method chaining and gives you access to the full set of configuration options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$loo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="s"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt; &lt;span class="p"&gt;['&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="p"&gt;']);&lt;/span&gt;
&lt;span class="nv"&gt;$loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Indent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Sortkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Theme&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;monokai&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Dump&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Data::Dumper Compatibility
&lt;/h2&gt;

&lt;p&gt;Loo implements the same accessor interface as &lt;code&gt;Data::Dumper&lt;/code&gt;: &lt;code&gt;Indent&lt;/code&gt;, &lt;code&gt;Terse&lt;/code&gt;, &lt;code&gt;Varname&lt;/code&gt;, &lt;code&gt;Useqq&lt;/code&gt;, &lt;code&gt;Quotekeys&lt;/code&gt;, &lt;code&gt;Sortkeys&lt;/code&gt;, &lt;code&gt;Maxdepth&lt;/code&gt;, &lt;code&gt;Maxrecurse&lt;/code&gt;, &lt;code&gt;Purity&lt;/code&gt;, &lt;code&gt;Deepcopy&lt;/code&gt;, &lt;code&gt;Pair&lt;/code&gt;, &lt;code&gt;Freezer&lt;/code&gt;, &lt;code&gt;Toaster&lt;/code&gt;, &lt;code&gt;Bless&lt;/code&gt;, &lt;code&gt;Deparse&lt;/code&gt;, &lt;code&gt;Trailingcomma&lt;/code&gt;, &lt;code&gt;Sparseseen&lt;/code&gt;, and &lt;code&gt;Pad&lt;/code&gt;. If you have existing code that configures a &lt;code&gt;Data::Dumper&lt;/code&gt; object, the same method calls work on a &lt;code&gt;Loo&lt;/code&gt; object and is &lt;strong&gt;faster&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Beyond that, Loo adds a few options that &lt;code&gt;Data::Dumper&lt;/code&gt; doesn't have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Indentwidth($n)&lt;/code&gt;&lt;/strong&gt; — control the number of characters per indent level (default 2)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Usetabs($bool)&lt;/code&gt;&lt;/strong&gt; — indent with tabs instead of spaces&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Colour Customisation
&lt;/h2&gt;

&lt;p&gt;Loo ships with four built-in themes: &lt;code&gt;default&lt;/code&gt;, &lt;code&gt;light&lt;/code&gt; (optimised for light terminal backgrounds), &lt;code&gt;monokai&lt;/code&gt;, and &lt;code&gt;none&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For fine-grained control, the &lt;code&gt;Colour&lt;/code&gt; method accepts a hash specifying foreground and background colours for 17 distinct syntax elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;$loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Colour&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s"&gt;string_fg&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;green&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;key_fg&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;magenta&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;number_fg&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cyan&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;brace_fg&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bright_black&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;undef_fg&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The colorable elements cover every visual component of the output: &lt;code&gt;string&lt;/code&gt;, &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;brace&lt;/code&gt;, &lt;code&gt;bracket&lt;/code&gt;, &lt;code&gt;paren&lt;/code&gt;, &lt;code&gt;arrow&lt;/code&gt;, &lt;code&gt;comma&lt;/code&gt;, &lt;code&gt;undef&lt;/code&gt;, &lt;code&gt;blessed&lt;/code&gt;, &lt;code&gt;regex&lt;/code&gt;, &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;variable&lt;/code&gt;, &lt;code&gt;quote&lt;/code&gt;, &lt;code&gt;keyword&lt;/code&gt;, &lt;code&gt;operator&lt;/code&gt;, and &lt;code&gt;comment&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Colour codes are pre-computed once at configuration time, so there's no per-character overhead during the dump.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deparser
&lt;/h2&gt;

&lt;p&gt;The most unusual feature of Loo is its built-in code deparser. When you enable deparsing, Loo walks the Perl op tree directly in C — the same internal structure that the Perl interpreter executes — and reconstructs Perl source code from it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$loo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nn"&gt;Some::&lt;/span&gt;&lt;span class="nv"&gt;function&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Deparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$loo&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;Dump&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means code references in your data structures are no longer black boxes. You see the actual code that will be run from the code. NOTE: It may not be identical to the code written as some postfix operations are lost as I am deparsing the compiled op tree itself.&lt;/p&gt;

&lt;p&gt;Getting this to work across Perl versions was one of the harder parts of the project. The op tree structure has changed across Perl releases — &lt;code&gt;OP_PADSV_STORE&lt;/code&gt; appeared in 5.38, &lt;code&gt;OP_EMPTYAVHV&lt;/code&gt; landed in 5.36 as an enum rather than a &lt;code&gt;#define&lt;/code&gt;, and &lt;code&gt;PADNAME&lt;/code&gt; typedefs shifted between 5.20 and 5.22. Loo handles all of this with version-conditional compilation, supporting Perl 5.10 through to the latest releases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reusable C Headers
&lt;/h2&gt;

&lt;p&gt;Loo's XS code is organised into modular C headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;loo.h&lt;/code&gt; — core definitions, themes, colour element names&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loo_colour.h&lt;/code&gt; — ANSI colour code generation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loo_escape.h&lt;/code&gt; — string escaping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loo_dump.h&lt;/code&gt; — recursive data structure dumping&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;loo_deparse.h&lt;/code&gt; — op tree walking and code reconstruction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These headers are installed alongside the Perl module, and &lt;code&gt;Loo-&amp;gt;include_dir&lt;/code&gt; returns their path. This means other XS modules can reuse Loo's colour or escaping logic without duplicating the C code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-Detection Done Right
&lt;/h2&gt;

&lt;p&gt;Loo follows the &lt;a href="https://no-color.org/" rel="noopener noreferrer"&gt;no-color.org&lt;/a&gt; convention and layers several checks to decide whether to emit ANSI codes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If &lt;code&gt;$Loo::USE_COLOUR&lt;/code&gt; is set, that takes precedence&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;$ENV{NO_COLOR}&lt;/code&gt; is set, colour is disabled&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;$ENV{TERM}&lt;/code&gt; is &lt;code&gt;"dumb"&lt;/code&gt;, colour is disabled&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;STDOUT&lt;/code&gt; is not a terminal, colour is disabled&lt;/li&gt;
&lt;li&gt;Otherwise, colour is enabled&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means &lt;code&gt;Dump()&lt;/code&gt; does the right thing whether you're debugging interactively, piping to a file, or running in CI. And &lt;code&gt;cDump()&lt;/code&gt; / &lt;code&gt;ncDump()&lt;/code&gt; are there when you need to override.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;Loo is available on CPAN:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cpanm Loo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It requires Perl 5.008003 or later and a C compiler (which you already have if you've ever installed an XS module).&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Data::Dumper&lt;/code&gt; is a workhorse that has served the Perl community well for decades. Loo isn't trying to replace it everywhere — but if you spend a lot of time reading dump output, colour and deparsing make a real difference. And if you're dumping large structures in production logging or tooling, the XS implementation gives you that output faster.&lt;/p&gt;

&lt;p&gt;Give it a look. Your eyes may thank you.&lt;/p&gt;

</description>
      <category>perl</category>
      <category>xs</category>
      <category>c</category>
      <category>programming</category>
    </item>
    <item>
      <title>Learning XS - Sharing Reusable C Headers Between Perl XS Distributions</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sun, 29 Mar 2026 12:00:05 +0000</pubDate>
      <link>https://dev.to/lnation/sharing-reusable-c-headers-between-perl-xs-distributions-2boj</link>
      <guid>https://dev.to/lnation/sharing-reusable-c-headers-between-perl-xs-distributions-2boj</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Since I last wrote a XS tutorial my knowledge within C has increased this has come from improvements in LLM software that has assisted in improving my knowledge where previously I would be stuck. This knowledge and software has since enabled me to craft more elegant and efficient XS implementations.&lt;/p&gt;

&lt;p&gt;Today I will share with you my technique for writing reusable C/XS code.&lt;/p&gt;

&lt;p&gt;One of the most powerful patterns in XS development is writing your core logic in pure C header files. This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-cost reuse&lt;/strong&gt; - no runtime linking, no shared libraries, just a &lt;code&gt;#include&lt;/code&gt; line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Perl dependency in the C layer&lt;/strong&gt; - your headers work in any C project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile-time inlining&lt;/strong&gt; - the compiler sees everything, optimises aggressively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple distribution&lt;/strong&gt; - headers are installed alongside the Perl module via &lt;code&gt;PM&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial walks through the complete pattern step by step, using a minimal working example you can build and run yourself.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Example
&lt;/h2&gt;

&lt;p&gt;We will create two distributions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Abacus&lt;/strong&gt; - a provider distribution that ships a reusable pure-C &lt;code&gt;abacus_math.h&lt;/code&gt; header containing simple arithmetic functions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tally&lt;/strong&gt; - a consumer distribution that &lt;code&gt;#include&lt;/code&gt;s the Abacus header to build its own XS module, without duplicating any C code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As always lets start by creating the distributions that we will need for this tutorial. Open your terminal and run module-starter. If you are using a modern version of Module::Starter then the command has changed slightly since my last posts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  module-starter &lt;span class="nt"&gt;--module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Abacus &lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LNATION &amp;lt;email@lnation.org&amp;gt;"&lt;/span&gt;
  module-starter &lt;span class="nt"&gt;--module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Tally &lt;span class="nt"&gt;--author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"LNATION &amp;lt;email@lnation.org&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 1: The Provider Distribution (Abacus)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Write the pure-C header
&lt;/h3&gt;

&lt;p&gt;This is the reusable part. It has &lt;strong&gt;zero Perl dependencies&lt;/strong&gt; - just standard C.&lt;/p&gt;

&lt;p&gt;Now enter the Abacus and create the include directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;cd &lt;/span&gt;Abucus
  &lt;span class="nb"&gt;mkdir &lt;/span&gt;include
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create a new file &lt;code&gt;abacus_math.h&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;touch &lt;/span&gt;abacus_math.h
  vim abacus_math.h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the following code into the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifndef ABACUS_MATH_H
#define ABACUS_MATH_H
&lt;/span&gt;
&lt;span class="cm"&gt;/*
 * abacus_math.h - Pure C arithmetic library (no Perl dependencies)
 *
 * This header is the reusable entry point for any C or XS project
 * that needs basic arithmetic operations. It has ZERO Perl/XS
 * dependencies.
 *
 * Usage from another XS module:
 *
 *     #include "abacus_math.h"
 *
 * Build: add -I/path/to/Abacus/include to your compiler flags.
 */&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;stdint.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* ── Error handling hook ─────────────────────────────────────────
 *
 * Consumers can #define ABACUS_FATAL(msg) before including this
 * header to route errors through their own mechanism.
 *
 * In an XS module you would typically do:
 *
 *     #define ABACUS_FATAL(msg) croak("%s", (msg))
 *     #include "abacus_math.h"
 *
 * In plain C the default behaviour is fprintf + abort.
 */&lt;/span&gt;
&lt;span class="cp"&gt;#ifndef ABACUS_FATAL
#  include &amp;lt;stdio.h&amp;gt;
#  include &amp;lt;stdlib.h&amp;gt;
#  define ABACUS_FATAL(msg) do { \
       fprintf(stderr, "abacus fatal: %s\n", (msg)); \
       abort(); \
   } while (0)
#endif
&lt;/span&gt;
&lt;span class="cm"&gt;/* ── Arithmetic operations ───────────────────────────────────── */&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;abacus_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;abacus_subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;abacus_multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;abacus_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ABACUS_FATAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"division by zero"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;abacus_factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ABACUS_FATAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"factorial of negative number"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cp"&gt;#endif &lt;/span&gt;&lt;span class="cm"&gt;/* ABACUS_MATH_H */&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above demonstrates three critical design patterns for reusable C headers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;static inline&lt;/code&gt; functions&lt;/strong&gt; eliminate linker complications by giving each translation unit its own copy of the function. The compiler can then inline these small arithmetic operations directly into the call site, producing zero-overhead abstractions. This is key to the "zero-cost reuse" principle—there is no shared library dependency, no function call overhead, just pure generated code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;ABACUS_FATAL&lt;/code&gt; macro hook&lt;/strong&gt; provides a customization point for error handling. By default, it calls &lt;code&gt;fprintf()&lt;/code&gt; and &lt;code&gt;abort()&lt;/code&gt; in standalone C programs; but consumers can &lt;code&gt;#define ABACUS_FATAL(msg) croak("%s", (msg))&lt;/code&gt; &lt;em&gt;before&lt;/em&gt; including the header to integrate seamlessly with Perl's exception system. This single mechanism allows the same C header to work across Perl XS, plain C, and other environments without code duplication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The use of only &lt;code&gt;stdint.h&lt;/code&gt; integers and no Perl types&lt;/strong&gt; ensures the header remains truly portable. There are no &lt;code&gt;SV*&lt;/code&gt; pointers, no &lt;code&gt;pTHX&lt;/code&gt; context variables, no &lt;code&gt;XSUB.h&lt;/code&gt; includes—just standard C99 types. This purity is what allows the header to be &lt;code&gt;#include&lt;/code&gt;d into any C or XS project without creating hidden Perl dependencies at the C layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write the Perl-facing XS header
&lt;/h3&gt;

&lt;p&gt;Next we will add another header which will hold the Perl/XS specific logic. Inside the include directory create a new file called &lt;code&gt;abacus.h&lt;/code&gt;. The rational behind this thin wrapper is to pull in Perl's headers and sets up the &lt;code&gt;ABACUS_FATAL&lt;/code&gt; macro to use &lt;code&gt;croak()&lt;/code&gt;. To reiterate only a XS distribution should include this header, whereas &lt;code&gt;abacus_math.h&lt;/code&gt; is generic and could be used by other languages which bind C.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    &lt;span class="nb"&gt;touch &lt;/span&gt;abacus.h
    vim abacus.h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paste the following code into the file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#ifndef ABACUS_H
#define ABACUS_H
&lt;/span&gt;
&lt;span class="cm"&gt;/*
 * abacus.h - Perl XS wrapper header for the Abacus library
 *
 * This header sets up Perl-specific error handling and includes
 * the pure C core library.
 *
 * For reuse from OTHER XS modules without Perl overhead, include
 * abacus_math.h directly instead (see that header for usage).
 */&lt;/span&gt;

&lt;span class="cp"&gt;#define PERL_NO_GET_CONTEXT
#include&lt;/span&gt; &lt;span class="cpf"&gt;"EXTERN.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"perl.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"XSUB.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"ppport.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* Route fatal errors through Perl's core croak() */&lt;/span&gt;
&lt;span class="cp"&gt;#define ABACUS_FATAL(msg) croak("%s", (msg))
&lt;/span&gt;
&lt;span class="cm"&gt;/* Pull in the pure-C library */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"abacus_math.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;#endif &lt;/span&gt;&lt;span class="cm"&gt;/* ABACUS_H */&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now any &lt;code&gt;.xs&lt;/code&gt; file can &lt;code&gt;#include "abacus.h"&lt;/code&gt; and get the full Perl/XS environment plus all the pure-C functions, with errors properly integrated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write the XS file
&lt;/h3&gt;

&lt;p&gt;Next we will create the XS file, return to the root directory and then enter the &lt;code&gt;lib&lt;/code&gt; directoy where you should see the &lt;code&gt;Abacus.pm&lt;/code&gt; file already. Create a new XS file called &lt;code&gt;Abacus.xs&lt;/code&gt;. This will be the glue that exposes the C functions to Perl.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nb"&gt;cd&lt;/span&gt; ../lib
  &lt;span class="nb"&gt;touch &lt;/span&gt;Abacus.xs
  vim Abacus.xs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"abacus.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="n"&gt;MODULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Abacus&lt;/span&gt;  &lt;span class="n"&gt;PACKAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Abacus&lt;/span&gt;

&lt;span class="n"&gt;PROTOTYPES&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DISABLE&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see we include &lt;code&gt;abacus.h&lt;/code&gt; which pulls in all the C that we need to create our XS module. We then define &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;subtract&lt;/code&gt;, &lt;code&gt;multiply&lt;/code&gt;, &lt;code&gt;divide&lt;/code&gt; and &lt;code&gt;factorial&lt;/code&gt; as XSUBs. As you should know by now XSUBs can be called directly from your perl code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write the Perl module with &lt;code&gt;include_dir()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Next open the pm file and update to add an exporter for the XSUBS we have just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Abacus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="mf"&gt;5.008003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;our&lt;/span&gt; &lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Exporter&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;our&lt;/span&gt; &lt;span class="nv"&gt;@EXPORT_OK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;qw(add subtract multiply divide factorial)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="nv"&gt;XSLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nn"&gt;XSLoader::&lt;/span&gt;&lt;span class="nv"&gt;load&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Abacus&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;$VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the critical piece that makes header sharing work. A &lt;code&gt;include_dir()&lt;/code&gt; method which returns the path to the installed headers so that consumer distributions can find them at build time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;include_dir&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$INC&lt;/span&gt;&lt;span class="p"&gt;{'&lt;/span&gt;&lt;span class="s1"&gt;Abacus.pm&lt;/span&gt;&lt;span class="p"&gt;'};&lt;/span&gt;
    &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="sr"&gt;s{Abacus\.pm$}{Abacus/include}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How &lt;code&gt;include_dir()&lt;/code&gt; works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When Perl loads &lt;code&gt;Abacus.pm&lt;/code&gt;, it records the full path in &lt;code&gt;%INC&lt;/code&gt;
(e.g. &lt;code&gt;/usr/lib/perl5/site_perl/Abacus.pm&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;include_dir()&lt;/code&gt; replaces &lt;code&gt;Abacus.pm&lt;/code&gt; with &lt;code&gt;Abacus/include&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;That directory exists because &lt;code&gt;Makefile.PL&lt;/code&gt; installs the headers there
(see next step)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Write the Makefile.PL that installs headers
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;PM&lt;/code&gt; hash is what makes headers available to other distributions after install. It maps source files to their installation destinations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Abacus/Makefile.PL&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="mf"&gt;5.008003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ExtUtils::&lt;/span&gt;&lt;span class="nv"&gt;MakeMaker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;WriteMakefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;NAME&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Abacus&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;AUTHOR&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Name &amp;lt;you@example.com&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;VERSION_FROM&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/Abacus.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;ABSTRACT_FROM&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/Abacus.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;LICENSE&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;artistic_2&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;MIN_PERL_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5.008003&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;CONFIGURE_REQUIRES&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExtUtils::MakeMaker&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;TEST_REQUIRES&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test::More&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;PREREQ_PM&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="s"&gt;XSMULTI&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# XS configuration&lt;/span&gt;
    &lt;span class="s"&gt;INC&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-I. -Iinclude&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;OBJECT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$(O_FILES)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;

    &lt;span class="c1"&gt;# *** THIS IS THE KEY PART ***&lt;/span&gt;
    &lt;span class="c1"&gt;# Install headers alongside the module so dependent&lt;/span&gt;
    &lt;span class="c1"&gt;# distributions can find them via Abacus-&amp;gt;include_dir()&lt;/span&gt;
    &lt;span class="s"&gt;PM&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/Abacus.pm&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$(INST_LIB)/Abacus.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include/abacus.h&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;        &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$(INST_LIB)/Abacus/include/abacus.h&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include/abacus_math.h&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$(INST_LIB)/Abacus/include/abacus_math.h&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="s"&gt;dist&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;COMPRESS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gzip -9f&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;SUFFIX&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;FILES&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Abacus-*&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;PM&lt;/code&gt; hash does two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installs &lt;code&gt;Abacus.pm&lt;/code&gt; as normal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copies the header files&lt;/strong&gt; into &lt;code&gt;Abacus/include/&lt;/code&gt; alongside the module&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After &lt;code&gt;make install&lt;/code&gt;, the filesystem looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;site_perl/
  Abacus.pm
  Abacus/
    include/
      abacus.h
      abacus_math.h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Write a test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Abacus/t/01-basic.t&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Test::&lt;/span&gt;&lt;span class="nv"&gt;More&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Abacus&lt;/span&gt; &lt;span class="sx"&gt;qw(add subtract multiply divide factorial)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;   &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;     &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nv"&gt;like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$@&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;qr/division by zero/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;divide by zero croaks&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nv"&gt;like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$@&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sx"&gt;qr/negative/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;negative factorial croaks&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;done_testing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build and install Abacus
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Abacus
perl Makefile.PL
make
make &lt;span class="nb"&gt;test
&lt;/span&gt;make &lt;span class="nb"&gt;install&lt;/span&gt;          &lt;span class="c"&gt;# installs headers into site_perl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 2: The Consumer Distribution (Tally)
&lt;/h2&gt;

&lt;p&gt;Tally is a separate distribution that reuses Abacus's C arithmetic without duplicating any code. It adds its own "running total" functionality on top.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write the Makefile.PL that finds Abacus headers
&lt;/h3&gt;

&lt;p&gt;This is where the consumer locates the provider's headers. The two-step resolution strategy supports both installed (CPAN) and development (sibling&lt;br&gt;
directory) scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Tally/Makefile.PL&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="mf"&gt;5.008003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;ExtUtils::&lt;/span&gt;&lt;span class="nv"&gt;MakeMaker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Resolve Abacus include directory:&lt;/span&gt;
&lt;span class="c1"&gt;#   1. Try installed Abacus module (CPAN / system)&lt;/span&gt;
&lt;span class="c1"&gt;#   2. Fall back to sibling directory (development)&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$abacus_inc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;no&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redefine&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
    &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;*&lt;/span&gt;&lt;span class="nn"&gt;XSLoader::&lt;/span&gt;&lt;span class="nv"&gt;load&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;  &lt;span class="c1"&gt;# skip XS bootstrap&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="nv"&gt;Abacus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Abacus&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;include_dir&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nv"&gt;$abacus_inc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;d&lt;/span&gt; &lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$abacus_inc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;d&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../Abacus/include&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$abacus_inc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../Abacus/include&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cannot find Abacus include directory.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Install Abacus or place it as a sibling directory.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nv"&gt;$abacus_inc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;WriteMakefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;NAME&lt;/span&gt;             &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tally&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;AUTHOR&lt;/span&gt;           &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Name &amp;lt;you@example.com&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;VERSION_FROM&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/Tally.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;ABSTRACT_FROM&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lib/Tally.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;LICENSE&lt;/span&gt;          &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;artistic_2&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;MIN_PERL_VERSION&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;5.008003&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;CONFIGURE_REQUIRES&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ExtUtils::MakeMaker&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Abacus&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt;              &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;TEST_REQUIRES&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test::More&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;PREREQ_PM&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Abacus&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="c1"&gt;# Point the compiler at Abacus's installed headers&lt;/span&gt;
    &lt;span class="s"&gt;INC&lt;/span&gt;    &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-I&lt;/span&gt;&lt;span class="si"&gt;$abacus_inc&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt;
    &lt;span class="s"&gt;OBJECT&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$(O_FILES)&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;

    &lt;span class="s"&gt;dist&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;COMPRESS&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gzip -9f&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;SUFFIX&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gz&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;FILES&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tally-*&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's walk through the header resolution:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Try the installed path first&lt;/strong&gt; - &lt;code&gt;require Abacus&lt;/code&gt; loads the module, then
&lt;code&gt;Abacus-&amp;gt;include_dir()&lt;/code&gt; returns the path where the headers were installed.
We stub out &lt;code&gt;XSLoader::load&lt;/code&gt; because we only need the pure-Perl &lt;code&gt;include_dir()&lt;/code&gt;
method, not the XS functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fall back to sibling directory&lt;/strong&gt; - during development, Abacus and Tally
often live side by side. &lt;code&gt;../Abacus/include&lt;/code&gt; handles this case.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Die with a clear message&lt;/strong&gt; if neither path works.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The resolved path is passed to &lt;code&gt;INC&lt;/code&gt;, which adds it to the C compiler's include search path (&lt;code&gt;-I/path/to/Abacus/include&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Abacus is listed in both &lt;code&gt;CONFIGURE_REQUIRES&lt;/code&gt; and &lt;code&gt;PREREQ_PM&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CONFIGURE_REQUIRES&lt;/code&gt; ensures Abacus is installed &lt;em&gt;before&lt;/em&gt; &lt;code&gt;Makefile.PL&lt;/code&gt; runs (needed because we &lt;code&gt;require Abacus&lt;/code&gt; at configure time)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PREREQ_PM&lt;/code&gt; ensures it is available at runtime too&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Write the XS file
&lt;/h3&gt;

&lt;p&gt;This is where the reuse happens. Tally includes &lt;code&gt;abacus_math.h&lt;/code&gt; directly -&lt;br&gt;
no Perl coupling, just pure C function calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Tally/Tally.xs&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define PERL_NO_GET_CONTEXT
#include&lt;/span&gt; &lt;span class="cpf"&gt;"EXTERN.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"perl.h"&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;"XSUB.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* Hook Abacus errors into Perl's croak() */&lt;/span&gt;
&lt;span class="cp"&gt;#define ABACUS_FATAL(msg) croak("%s", (msg))
&lt;/span&gt;
&lt;span class="cm"&gt;/* Include the pure-C header from Abacus - no Perl deps in the header */&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"abacus_math.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* ── Tally's own C logic, built on top of Abacus ─────────────── */&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;tally_state_t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;tally_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;tally_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;tally_subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;tally_multiply_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;abacus_multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;int32_t&lt;/span&gt;
&lt;span class="nf"&gt;tally_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kr"&gt;inline&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;tally_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* ── XS bindings ─────────────────────────────────────────────── */&lt;/span&gt;

&lt;span class="n"&gt;MODULE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tally&lt;/span&gt;  &lt;span class="n"&gt;PACKAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Tally&lt;/span&gt;

&lt;span class="n"&gt;PROTOTYPES&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DISABLE&lt;/span&gt;

&lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Newxz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tally_state_t&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;tally_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;sv_setref_pv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RETVAL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INT2PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tally_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INT2PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tally_subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;multiply_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INT2PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tally_multiply_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INT2PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tally_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;RETVAL&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INT2PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;tally_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;
&lt;span class="nf"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;
  &lt;span class="n"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INT2PTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tally_state_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SvIV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="n"&gt;Safefree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that Tally includes &lt;code&gt;abacus_math.h&lt;/code&gt; (the &lt;strong&gt;pure C&lt;/strong&gt; header), not &lt;code&gt;abacus.h&lt;/code&gt; (the Perl-facing wrapper). This is intentional - Tally has its own Perl/XS setup and only needs the C functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write the Perl module
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Tally/lib/Tally.pm&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Tally&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="mf"&gt;5.008003&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;our&lt;/span&gt; &lt;span class="nv"&gt;$VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="nv"&gt;XSLoader&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nn"&gt;XSLoader::&lt;/span&gt;&lt;span class="nv"&gt;load&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Tally&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="nv"&gt;$VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cp"&gt;__END__

=head1 NAME

Tally - Running total calculator using Abacus C headers

=head1 SYNOPSIS

    use Tally;

    my $t = Tally-&amp;gt;new;
    $t-&amp;gt;add(10);        # total is now 10
    $t-&amp;gt;add(5);         # total is now 15
    $t-&amp;gt;subtract(3);    # total is now 12
    $t-&amp;gt;multiply_total(2);  # total is now 24
    say $t-&amp;gt;total;      # 24
    $t-&amp;gt;reset;          # back to 0

=cut
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Write a test
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Tally/t/01-basic.t&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Test::&lt;/span&gt;&lt;span class="nv"&gt;More&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;use_ok&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;Tally&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Tally&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;isa_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tally&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;starts at zero&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add 10&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add 5&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subtract 3&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;multiply_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;multiply by 2&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;total is 24&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reset to zero&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;done_testing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Build Tally (development mode)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;Tally
perl Makefile.PL     &lt;span class="c"&gt;# finds ../Abacus/include automatically&lt;/span&gt;
make
make &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope you found this tutorial useful! If you have questions about XS, C header reuse, or building modular Perl/C libraries, please leave a message. &lt;/p&gt;

</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Horus, Apophis, and Sekhmet: An C/XS Identifier Stack for Perl</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sun, 29 Mar 2026 09:52:09 +0000</pubDate>
      <link>https://dev.to/lnationorg/horus-apophis-and-sekhmet-an-cxs-identifier-stack-for-perl-1ac3</link>
      <guid>https://dev.to/lnationorg/horus-apophis-and-sekhmet-an-cxs-identifier-stack-for-perl-1ac3</guid>
      <description>&lt;p&gt;Three modules, one goal: fast, correct identifier generation in Perl with zero runtime dependencies. Horus generates UUIDs. Sekhmet generates ULIDs. Apophis uses deterministic UUIDs to build content-addressable storage. All three are implemented in C, exposed through XS, and designed to work together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Horus: Every UUID Version, One Module
&lt;/h2&gt;

&lt;p&gt;Horus implements all UUID versions defined in RFC 9562 -- v1 through v8, plus NIL and MAX. The entire engine is C, compiled once, called millions of times per second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Horus&lt;/span&gt; &lt;span class="sx"&gt;qw(:all)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$random&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                         &lt;span class="c1"&gt;# 122 random bits&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$sortable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v7&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                         &lt;span class="c1"&gt;# timestamp + random, sortable&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$fixed&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;UUID_NS_DNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt; &lt;span class="c1"&gt;# deterministic, always the same&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why multiple versions matter
&lt;/h3&gt;

&lt;p&gt;Each version solves a different problem:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v4&lt;/strong&gt; is the workhorse. 122 bits of randomness, no coordination needed. Use it for session tokens, request IDs, anything where uniqueness is all you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v7&lt;/strong&gt; embeds a millisecond timestamp in the high bits, making UUIDs lexicographically sortable. Database indexes love this -- new rows append instead of scattering across B-tree pages. Horus guarantees monotonic ordering within the same millisecond.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;uuid_v7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;# 019d38f6-3e9a-765c-ae1c-1cfeb0c30000&lt;/span&gt;
&lt;span class="c1"&gt;# 019d38f6-3e9a-765c-ae1c-1cfeb0c40000&lt;/span&gt;
&lt;span class="c1"&gt;# 019d38f6-3e9a-765c-ae1c-1cfeb0c50000&lt;/span&gt;
&lt;span class="c1"&gt;# String sort == chronological sort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;v5&lt;/strong&gt; is deterministic. Given the same namespace and name, it always produces the same UUID. This is the foundation Apophis builds on... the same content, the same identifier, every time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;UUID_NS_DNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;UUID_NS_DNS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;example.com&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
&lt;span class="c1"&gt;# $a eq $b -- always&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ten output formats
&lt;/h3&gt;

&lt;p&gt;Every generator accepts a format parameter. Convert between them freely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;uuid_convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;UUID_FMT_STR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;# 550e8400-e29b-41d4-a716-446655440000&lt;/span&gt;
&lt;span class="nv"&gt;uuid_convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;UUID_FMT_HEX&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;# 550e8400e29b41d4a716446655440000&lt;/span&gt;
&lt;span class="nv"&gt;uuid_convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;UUID_FMT_BRACES&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;# {550e8400-e29b-41d4-a716-446655440000}&lt;/span&gt;
&lt;span class="nv"&gt;uuid_convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;UUID_FMT_URN&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;# urn:uuid:550e8400-e29b-41d4-a716-446655440000&lt;/span&gt;
&lt;span class="nv"&gt;uuid_convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;UUID_FMT_BASE64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;# VQ6EAOKbQdSnFkRmVUQAAA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bulk generation
&lt;/h3&gt;

&lt;p&gt;When you need thousands of IDs, crossing the Perl/C boundary once beats crossing it thousands of times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v4_bulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;# single call, 10k UUIDs back&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Utilities
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="nv"&gt;uuid_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;# is this a valid UUID?&lt;/span&gt;
&lt;span class="nv"&gt;uuid_version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$string&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;# which version? (1-8)&lt;/span&gt;
&lt;span class="nv"&gt;uuid_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$v7_uuid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;             &lt;span class="c1"&gt;# extract epoch seconds from v7/v6&lt;/span&gt;
&lt;span class="nv"&gt;uuid_cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                &lt;span class="c1"&gt;# sort comparison (-1, 0, 1)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sekhmet: ULIDs for When You Need Sortable and Compact
&lt;/h2&gt;

&lt;p&gt;A ULID is 26 characters of Crockford base32 encoding: 10 characters of millisecond timestamp followed by 16 characters of randomness. They sort lexicographically by time, they are URL-safe, and they are shorter than UUIDs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Sekhmet&lt;/span&gt; &lt;span class="sx"&gt;qw(:all)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ulid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;# 06EKHXHYKAT25K0YQJHN6A6YJR&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Monotonic mode
&lt;/h3&gt;

&lt;p&gt;If you generate multiple ULIDs within the same millisecond, the random component increments to guarantee strict ordering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_monotonic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_monotonic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_monotonic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;# $a lt $b lt $c -- guaranteed, even within the same millisecond&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Time extraction
&lt;/h3&gt;

&lt;p&gt;The timestamp is baked into the ULID. Extract it without a database lookup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ulid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$epoch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ulid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;       &lt;span class="c1"&gt;# 1774777155.226&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ms&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_time_ms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ulid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;# 1774777155226&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  UUID interoperability
&lt;/h3&gt;

&lt;p&gt;ULIDs and UUID v7 share the same structure -- 48-bit timestamp, random fill. Convert between them losslessly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$ulid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_to_uuid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ulid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;# standard UUID v7 string&lt;/span&gt;
&lt;span class="c1"&gt;# Useful when your API expects UUIDs but you generate ULIDs internally&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  When to use Sekhmet vs Horus
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;Sekhmet&lt;/strong&gt; (&lt;code&gt;ulid()&lt;/code&gt;) when you want compact, sortable, human friendly identifiers for log entries, event streams, anything displayed in a UI. Use &lt;strong&gt;Horus&lt;/strong&gt; (&lt;code&gt;uuid_v7()&lt;/code&gt;) when you need standard UUID format for compatibility with systems that expect 36-character hyphenated strings. Use &lt;strong&gt;Horus&lt;/strong&gt;&lt;br&gt;
(&lt;code&gt;uuid_v4()&lt;/code&gt;) when you need pure randomness with no timestamp leakage.&lt;/p&gt;
&lt;h2&gt;
  
  
  Apophis: Content-Addressable Storage
&lt;/h2&gt;

&lt;p&gt;Apophis answers the question: "Have I seen this content before?" It hashes content with UUID v5 to produce a deterministic identifier, then stores the content in a sharded directory tree. Same content always maps to the same path. Different content never collides.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Apophis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Apophis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;store_dir&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/var/data/cas&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
&lt;span class="c1"&gt;# 3e856e0f-c7ac-569e-827b-40df723c326f&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$id2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;
&lt;span class="c1"&gt;# 3e856e0f-c7ac-569e-827b-40df723c326f  -- same content, same ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How storage works
&lt;/h3&gt;

&lt;p&gt;Content is stored in a two-level hex-sharded directory tree derived from the UUID. The first four hex characters become two directory levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/var/data/cas/
  3e/85/3e856e0f-c7ac-569e-827b-40df723c326f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives 65,536 possible directories -- enough to keep any single directory from growing too large, even with millions of files.&lt;/p&gt;

&lt;p&gt;Writes are atomic: content goes to a temporary file first, then is renamed into place. A crash mid-write leaves no partial files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identification without storage
&lt;/h3&gt;

&lt;p&gt;Sometimes you just want the identifier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;some content&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;     &lt;span class="c1"&gt;# UUID, no write&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;identify_file&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;/path/to/big.iso&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;  &lt;span class="c1"&gt;# streams in 64KB chunks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;File identification is streaming -- a 10GB file uses the same memory as a 10KB file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Metadata
&lt;/h3&gt;

&lt;p&gt;Attach arbitrary metadata as a sidecar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image data&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;mime_type&lt;/span&gt;     &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;original_name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;photo.png&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;uploaded_by&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user-42&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$meta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;# { mime_type =&amp;gt; 'image/png', original_name =&amp;gt; 'photo.png', ... }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Namespace isolation
&lt;/h3&gt;

&lt;p&gt;The namespace parameter creates a separate UUID v5 namespace. The same content under different namespaces produces different identifiers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Apophis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uploads&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Apophis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="p"&gt;")&lt;/span&gt; &lt;span class="ow"&gt;ne&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;  &lt;span class="c1"&gt;# different IDs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets you run multiple independent stores without collision.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verification
&lt;/h3&gt;

&lt;p&gt;Content-addressable storage has a built-in integrity check: re-hash the content and compare to the filename.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# content matches its identifier -- no corruption&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How They Fit Together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Horus (Foundation)
  |-- UUID v1-v8, NIL, MAX
  |-- C headers reused by downstream XS modules
  |
  |--- Apophis (Content-addressable storage)
  |      Uses UUID v5 for deterministic content identification
  |
  |--- Sekhmet (ULID generation)
         Uses Horus C primitives for Crockford base32, CSPRNG, timestamps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Horus is the foundation. Its C headers are standalone -- no Perl types, no interpreter context. Apophis and Sekhmet include them at compile time via &lt;code&gt;Horus-&amp;gt;include_dir()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A practical example using all three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Horus&lt;/span&gt; &lt;span class="sx"&gt;qw(:all)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Apophis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Sekhmet&lt;/span&gt; &lt;span class="sx"&gt;qw(:all)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Event tracking system&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$event_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_monotonic&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;              &lt;span class="c1"&gt;# sortable event identifier&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;uuid_v4&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                     &lt;span class="c1"&gt;# random session token&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Apophis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;events&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;store_dir&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/var/events&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="c1"&gt;# Store event payload, get content-addressable ID&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;encode_json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button-1&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$content_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;\&lt;/span&gt;&lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;meta&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;event_id&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;session_id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;timestamp&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;ulid_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;# Later: retrieve by content hash&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$store&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$content_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Or find when the event happened from the ULID&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ulid_time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$event_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each module handles one concern well. Horus generates identifiers. Sekhmet adds time sortable compact identifiers. Apophis maps content to identifiers and manages storage. No module tries to do what another already does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;All three modules use custom ops on Perl 5.14+ to eliminate subroutine dispatch overhead. The hot paths are pure C with no Perl API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cpanm Horus
cpanm Sekhmet
cpanm Apophis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three are on &lt;a href="https://metacpan.org/author/LNATION" rel="noopener noreferrer"&gt;CPAN&lt;/a&gt; under the&lt;br&gt;
Artistic License 2.0.&lt;/p&gt;

</description>
      <category>perl</category>
      <category>c</category>
      <category>xs</category>
      <category>programming</category>
    </item>
    <item>
      <title>Eshu: Indentation Fixer for Eight Languages, Written in C</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sun, 29 Mar 2026 07:03:48 +0000</pubDate>
      <link>https://dev.to/lnationorg/eshu-indentation-fixer-for-eight-languages-written-in-c-3fm6</link>
      <guid>https://dev.to/lnationorg/eshu-indentation-fixer-for-eight-languages-written-in-c-3fm6</guid>
      <description>&lt;p&gt;Most code formatters want to own your style. They have opinions about brace placement, line length, trailing commas, and a hundred other things you never asked for. Sometimes you just want the indentation fixed. Tabs consistent, nesting correct, content untouched.&lt;/p&gt;

&lt;p&gt;That is was the goal for &lt;strong&gt;Eshu&lt;/strong&gt; and exactly what it does. It reads source code line by line, tracks nesting depth through a state machine, rewrites the leading whitespace, and leaves everything else alone. The entire engine is written in C, exposed to Perl through XS, and ships with a CLI that can check, diff, or fix files in place. The distribution also ships with a vim plugin as that is my editor of choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Eight languages, one tool
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;C&lt;/code&gt; | &lt;code&gt;Perl&lt;/code&gt; | &lt;code&gt;XS&lt;/code&gt; | &lt;code&gt;XML&lt;/code&gt; | &lt;code&gt;HTML&lt;/code&gt; | &lt;code&gt;CSS&lt;/code&gt; | &lt;code&gt;JavaScript&lt;/code&gt; | &lt;code&gt;POD&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each language gets its own scanner with its own state machine. They share a common architecture which track depth, emit indentation and advance but each state machine actually handles the constructs that make the specific language awkward to indent.&lt;/p&gt;

&lt;p&gt;Perl has heredocs, quoted constructs (&lt;code&gt;qw()&lt;/code&gt;, &lt;code&gt;qq{}&lt;/code&gt;, &lt;code&gt;s///&lt;/code&gt;), and embedded&lt;br&gt;
POD. JavaScript has template literals with nested &lt;code&gt;${}&lt;/code&gt; interpolation. XS&lt;br&gt;
files switch between C and Perl conventions at the &lt;code&gt;MODULE =&lt;/code&gt; boundary. HTML&lt;br&gt;
has void elements and verbatim zones inside &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. Each of these needs specific handling, and getting any of them wrong means corrupting content visually.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why C?
&lt;/h2&gt;

&lt;p&gt;Indentation fixing is embarrassingly linear. You read a line, update state, emit the line with new leading whitespace, repeat. There is no tree to build, no AST to walk, no multipass resolution. A single-pass scanner in C processes a large codebase in milliseconds.&lt;/p&gt;

&lt;p&gt;The engine is implemented entirely in standalone C header files -- ten of them, roughly 3,600 lines total. They have no Perl dependencies. No &lt;code&gt;SV*&lt;/code&gt;, no &lt;code&gt;croak()&lt;/code&gt;, no interpreter context. Just &lt;code&gt;stdlib.h&lt;/code&gt;, &lt;code&gt;string.h&lt;/code&gt;, and &lt;code&gt;ctype.h&lt;/code&gt;. This means they can be reused from any C program or language that can bind them, not just my Perl XS modules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;include/
  eshu.h              Core types, config, buffer, language enum
  eshu_c.h            C scanner
  eshu_pl.h           Perl scanner (heredoc, regex, qw, POD)
  eshu_xs.h           XS dual-mode scanner
  eshu_xml.h          XML/HTML scanner
  eshu_css.h          CSS scanner
  eshu_js.h           JavaScript scanner (template literals)
  eshu_pod.h          POD scanner
  eshu_file.h         File I/O, directory walking, binary detection
  eshu_diff.h         Unified diff generation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How the scanner works
&lt;/h2&gt;

&lt;p&gt;Every language scanner follows the same pattern. For each line of input:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pre-adjust depth --&amp;gt; emit indent --&amp;gt; copy content --&amp;gt; post-adjust depth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A closing brace on a line means you dedent &lt;em&gt;before&lt;/em&gt; emitting that line. An opening brace means you indent the &lt;em&gt;next&lt;/em&gt; line. The scanner maintains a state enum to know whether it is inside a string, a comment, a heredoc, a regex, or regular code. State transitions happen character by character within the scan function; depth changes happen at line boundaries.&lt;/p&gt;

&lt;p&gt;The Perl scanner, for example, tracks 14 distinct states: regular code, double-quoted strings, single-quoted strings, regex, heredoc (both standard and indented), &lt;code&gt;qw&lt;/code&gt;, &lt;code&gt;qq&lt;/code&gt;, &lt;code&gt;q&lt;/code&gt;, POD, line comments, and block comments. It detects heredoc terminators, remembers whether the variant is indented (&lt;code&gt;&amp;lt;&amp;lt;~EOF&lt;/code&gt;), buffers the body verbatim, and resumes normal scanning after the terminator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Perl: Is &lt;code&gt;/&lt;/code&gt; division or regex?
&lt;/h3&gt;

&lt;p&gt;The classic Perl parsing problem. Eshu tracks whether the previous meaningful token was a value (a variable, a closing bracket, a number) or an operator. If it was a value, &lt;code&gt;/&lt;/code&gt; is division. Otherwise, it opens a regex. This is the same heuristic that syntax highlighters use, and it covers real-world code well.&lt;/p&gt;

&lt;h3&gt;
  
  
  XS: Two languages in one file
&lt;/h3&gt;

&lt;p&gt;An XS file is C code at the top and a Perl/C hybrid below &lt;code&gt;MODULE =&lt;/code&gt;. Eshu detects the boundary and switches scanners. Below the boundary, it tracks XSUB blocks (each new function declaration resets depth), labels like &lt;code&gt;CODE:&lt;/code&gt;, &lt;code&gt;OUTPUT:&lt;/code&gt;, and &lt;code&gt;INIT:&lt;/code&gt;, and special cases like &lt;code&gt;BOOT:&lt;/code&gt; sections that use shallower indentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript: Template literals with nested interpolation
&lt;/h3&gt;

&lt;p&gt;A backtick string in JavaScript can contain &lt;code&gt;${expr}&lt;/code&gt;, and that expression can contain braces, function calls, even another template literal. Eshu maintains a depth counter for interpolation braces so it knows when the &lt;code&gt;}&lt;/code&gt; closes the interpolation versus when it closes a block inside the interpolation.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML: Script blocks need JavaScript rules
&lt;/h3&gt;

&lt;p&gt;Content inside &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; tags is JavaScript, not HTML. Eshu collects the entire script block, passes it through the JavaScript scanner for reindentation, then splices it back at the correct HTML depth. The same applies to recognising void elements (&lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt;, etc.) that should not increase nesting depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CLI
&lt;/h2&gt;

&lt;p&gt;Eshu ships with a command-line tool that supports the three modes you actually need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Preview what would change&lt;/span&gt;
eshu &lt;span class="nt"&gt;--diff&lt;/span&gt; lib/

&lt;span class="c"&gt;# Check in CI (exit 1 if anything needs fixing)&lt;/span&gt;
eshu &lt;span class="nt"&gt;--check&lt;/span&gt; lib/ t/

&lt;span class="c"&gt;# Fix in place&lt;/span&gt;
eshu &lt;span class="nt"&gt;--fix&lt;/span&gt; lib/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, nothing is modified. You have to explicitly ask for &lt;code&gt;--fix&lt;/code&gt;. Language is detected from file extensions, but can be overridden with &lt;code&gt;--lang&lt;/code&gt;. You can filter files with &lt;code&gt;--exclude&lt;/code&gt; and &lt;code&gt;--include&lt;/code&gt; (regex patterns), choose tabs or spaces, set the indent width, and even restrict processing to a line range within a file.&lt;/p&gt;

&lt;p&gt;Directory processing is recursive by default, skips binary files (detected by sampling the first 8KB for NUL bytes), respects a 1MB size limit, and follows file symlinks but not directory symlinks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Perl API
&lt;/h2&gt;

&lt;p&gt;Everything the CLI does is available programmatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Eshu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Fix a string&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$fixed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Eshu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;indent_pl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;spaces&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Auto-detect language and process&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Eshu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;indent_file&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;lib/App.pm&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;fix&lt;/span&gt;  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;diff&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="ow"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="c1"&gt;# Process an entire directory&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Eshu&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;indent_dir&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;lib/&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="s"&gt;fix&lt;/span&gt;       &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;recursive&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;exclude&lt;/span&gt;   &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sx"&gt;qr/\.bak$/&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;say&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$report&lt;/span&gt;&lt;span class="s2"&gt;-&amp;gt;{files_changed} files fixed&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each language also has a direct method: &lt;code&gt;indent_c&lt;/code&gt;, &lt;code&gt;indent_xs&lt;/code&gt;, &lt;code&gt;indent_xml&lt;/code&gt;, &lt;code&gt;indent_html&lt;/code&gt;, &lt;code&gt;indent_css&lt;/code&gt;, &lt;code&gt;indent_js&lt;/code&gt;, &lt;code&gt;indent_pod&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Idempotent by design
&lt;/h2&gt;

&lt;p&gt;Running Eshu twice produces the same result as running it once. This is not just a goal, it is tested. The test suite includes real-world Perl example, verifies that processing them does not crash, and asserts that a second pass produces identical output.&lt;/p&gt;

&lt;p&gt;This matters for CI integration. If &lt;code&gt;eshu --check&lt;/code&gt; passes, you know that running &lt;code&gt;eshu --fix&lt;/code&gt; would be a no-op. There is no oscillation, no cascading reformats, no "fix the fix" loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;p&gt;Eshu does not reformat code. It does not move braces, break long lines, sort imports, add or remove semicolons, or have opinions about blank lines. It touches leading whitespace and nothing else. Diffs are clean: every changed line shows only the whitespace prefix changing.&lt;/p&gt;

&lt;p&gt;This is a deliberate constraint. A tool that only fixes indentation is a tool you can run on any codebase without fear. It will not start a style war. It will not produce a 10,000 line diff that buries your actual changes. It will make the nesting visible and get out of the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vim integration
&lt;/h2&gt;

&lt;p&gt;Eshu ships with a Vim plugin in the distribution. It pipes the current buffer through &lt;code&gt;eshu&lt;/code&gt; and replaces the content in place, with automatic language detection and cursor position preservation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;The easiest way with Vim 8+ native packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.vim/pack/eshu/start
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /path/to/Eshu/vim ~/.vim/pack/eshu/start/eshu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Neovim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.local/share/nvim/site/pack/eshu/start
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /path/to/Eshu/vim ~/.local/share/nvim/site/pack/eshu/start/eshu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or if you prefer vim-plug:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;Plug &lt;span class="s1"&gt;'/path/to/Eshu/vim'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just source it directly in your &lt;code&gt;.vimrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="sr"&gt;/path/&lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="sr"&gt;/Eshu/&lt;/span&gt;&lt;span class="k"&gt;vim&lt;/span&gt;&lt;span class="sr"&gt;/plugin/&lt;/span&gt;eshu&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;vim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;Once loaded, you get two commands and a default keybinding:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:EshuFix&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Fix indentation for the entire file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;:EshuFixRange&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Fix indentation for the selected lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\ef&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Normal&lt;/td&gt;
&lt;td&gt;Fix entire file (default &lt;code&gt;&amp;lt;Leader&amp;gt;ef&lt;/code&gt; mapping)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;\ef&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Visual&lt;/td&gt;
&lt;td&gt;Fix selected lines (default &lt;code&gt;&amp;lt;Leader&amp;gt;ef&lt;/code&gt; mapping)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The plugin detects the language from the file extension -- &lt;code&gt;.pm&lt;/code&gt; and &lt;code&gt;.pl&lt;/code&gt; map to Perl, &lt;code&gt;.xs&lt;/code&gt; to XS, &lt;code&gt;.html&lt;/code&gt; to HTML, and so on. If the &lt;code&gt;eshu&lt;/code&gt; binary is not in your &lt;code&gt;$PATH&lt;/code&gt;, point the plugin at it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:eshu_cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'/path/to/Eshu/bin/eshu'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To disable the default mappings and use your own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;g:eshu_no_mappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
nnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;F6&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;EshuFix&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;CR&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
vnoremap &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;F6&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;EshuFixRange&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;CR&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eshu is available on &lt;a href="https://metacpan.org/pod/Eshu" rel="noopener noreferrer"&gt;CPAN&lt;/a&gt; under the Artistic License 2.0.&lt;/p&gt;

</description>
      <category>perl</category>
      <category>xs</category>
      <category>c</category>
      <category>programming</category>
    </item>
    <item>
      <title>Ready, Set, Compile... you slow Camel</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sun, 25 Jan 2026 13:47:26 +0000</pubDate>
      <link>https://dev.to/lnationorg/ready-set-compile-you-slow-camel-1nbf</link>
      <guid>https://dev.to/lnationorg/ready-set-compile-you-slow-camel-1nbf</guid>
      <description>&lt;p&gt;"Perl is slow."&lt;/p&gt;

&lt;p&gt;I've heard this for years, well since I started. You probably have too. And honestly? For a long time, I didn't have a great rebuttal. Sure, Perl's fast &lt;em&gt;enough&lt;/em&gt; for most things, it's well known for text processing, glueing code and quick scripts. But when it came to object heavy code, the critics have a point.&lt;/p&gt;

&lt;p&gt;We will begin by looking at the myth of perl being slow a little more deeply. Here's a benchmark between Perl and Python using CPU seconds, a fair comparison that measures actual work done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== PERL (5 CPU seconds per test) ===
Integer arithmetic             1,072,800/s
Float arithmetic                 398,800/s
String concat                    970,000/s
Array push/iterate               368,800/s
Hash insert/iterate               84,800/s
Function calls                   244,000/s
Regex match                   12,921,200/s

=== PYTHON (5 CPU seconds per test) ===
Integer arithmetic               777,200/s
Float arithmetic                 512,400/s
String concat                    627,200/s
List append/iterate              476,400/s
Dict insert/iterate              140,600/s
Function calls                   331,400/s
Regex match                   10,543,713/s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results are more nuanced than the "Perl is slow" narrative suggests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;th&gt;Margin&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integer arithmetic&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Perl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.4x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Float arithmetic&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;1.3x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String concat&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Perl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.5x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array/List ops&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;1.3x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash/Dict ops&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;1.7x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Function calls&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;1.4x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regex match&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Perl&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.2x faster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Perl wins at what it's always been good at: integers, strings, and regex. Python wins at floats, data structures, and function calls areas where I am told Python 3.x has seen heavy optimisation work.&lt;/p&gt;

&lt;p&gt;But here's the thing that surprised me: neither language is dramatically faster than the other for basic operations. The differences are measured in fractions, not orders of magnitude. So where does the "Perl is slow" reputation actually come from?&lt;/p&gt;

&lt;p&gt;Object-oriented code. Let's run that same fair comparison:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== Object creation + 2 method calls (5M iterations) ===
Perl bless:    4,155,178/s  (1.20 sec)
Python class:  5,781,818/s  (0.86 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okay, this is not so bad. Perl's only 40% behind. But now let's look at what people &lt;em&gt;actually&lt;/em&gt; use these days: Moo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=== Object creation + 2 method calls (5M iterations) ===
Perl bless:    4,176,222/s  (1.20 sec)
Moo class:       843,708/s  (5.93 sec)
Python class:  5,590,052/s  (0.89 sec)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, what? Moo is &lt;strong&gt;6.6x slower than Python&lt;/strong&gt;. And it's &lt;strong&gt;5x slower than plain bless&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is layered with actual business logic is I guess where "Perl is slow" actually comes from. This all comes down to layers. Every Moo accessor has been optimised but if you have all features you build a call stack, each adding overhead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$obj-&amp;gt;name
  └─&amp;gt; accessor method (generated sub)
        └─&amp;gt; type constraint check
              └─&amp;gt; coercion check
                    └─&amp;gt; trigger check
                          └─&amp;gt; lazy builder check
                                └─&amp;gt; finally: $self-&amp;gt;{name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of those subroutine calls means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Push arguments onto the stack&lt;/strong&gt; (~3-5 ops)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a new scope&lt;/strong&gt; (localizing variables)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute the check&lt;/strong&gt; (even if it's just "return true")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pop the stack and return&lt;/strong&gt; (~3-5 ops)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even a "simple" Moo accessor with just a type constraint involves roughly &lt;strong&gt;30+ additional operations&lt;/strong&gt; compared to a plain hash access. The type constraint alone might call:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;has_type_constraint()&lt;/code&gt; - is there a constraint?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type_constraint()&lt;/code&gt; - get the constraint object&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check()&lt;/code&gt; - call the constraint's check method&lt;/li&gt;
&lt;li&gt;The actual validation logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Multiply that by two accessors per iteration, five million iterations, and suddenly you're spending 5 seconds instead of 1.&lt;/p&gt;

&lt;p&gt;This is the trade off Moo makes: flexibility and safety for speed. And for most applications, it's the right trade off and even in python they do this with what they call pydantic that halfs the performance of python objects.&lt;/p&gt;

&lt;p&gt;I've spent more time than I'd care to admit thinking about this question. Not in a "let's rewrite everything in Rust" kind of way, but genuinely asking: what would it take to make Perl's object system competitive with languages people actually consider fast?&lt;/p&gt;

&lt;p&gt;The answer, it turns out, was inside a CPAN module first released on 'Mon Jul 24 11:23:25 2000'. This was highlighted to me by another works who I am indeed one of the three people who do not only read their blogs but also often finds themselves lost within their interesting coding patterns.&lt;/p&gt;

&lt;p&gt;So this is the story of the four modules that changed how I think about Perl performance: Marlin, Meow, Inline and XS::JIT. They're different tools with different philosophies, but together they represent something I never quite expected to see Perl object access that's actually &lt;em&gt;faster&lt;/em&gt; than Python's equivalent. Not "almost as fast." Faster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Marlin story: A faster fish in the Moose family
&lt;/h2&gt;

&lt;p&gt;If you've written any serious Perl in the last fifteen years, you've probably used Moose. Or Moo. Or Mouse. The naming convention is... well, it's a thing we do now.&lt;/p&gt;

&lt;p&gt;Marlin fits right into that tradition, and the name's not accidental. Marlins are among the fastest fish in the ocean. That's the pitch: everything you love about Moose-style OO, but with speed as a first-class concern.&lt;/p&gt;

&lt;p&gt;Toby Inkster released Marlin in late 2025, and it caught my attention as I stated before, many of his projects do. I'd previously attempted to write a fast OO system myself (Meow), but was struggling to even compete with Moo despite being entirely XS. Partly ability, partly still learning, mostly not being in the right compile time stage.&lt;/p&gt;

&lt;p&gt;With my interest piqued, I installed Marlin, played with the API, and ran some benchmarks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark: 1,000,000 iterations
            Rate   Meow    Moo Marlin  Mouse
Meow    606,061/s     --    -1%   -45%   -47%
Moo     609,756/s     1%     --   -45%   -46%
Marlin 1,098,901/s    81%    80%     --    -3%
Mouse  1,136,364/s    87%    86%     3%     --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Marlin performed well. Meow at that point was... not impressive. But I liked Marlin's API and, understanding my own implementation's limitations, I was satisfied enough with the speed to build my Claude modules around it, while also understanding it would likely improve in performance.&lt;/p&gt;

&lt;p&gt;A few weeks later, and a lot happened in between, but on Friday evening I randomly decided to revisit my Meow directory. Could I fix some of the flaws based upon my recent learnings? I managed to, and saw a huge improvement in my own benchmarks. So I updated to the latest Marlin for a fair comparison.&lt;/p&gt;

&lt;p&gt;I was expecting Meow to be faster now since I'm doing much less in this minimalist approach. But what I actually found surprised me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark: 10,000,000 iterations
            Rate    Moo  Mouse   Meow Marlin
Moo     868,810/s     --   -47%   -60%   -81%
Mouse  1,626,016/s    87%     --   -26%   -64%
Meow   2,183,406/s   151%    34%     --   -52%
Marlin 4,504,505/s   418%   177%   106%     --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Marlin had gotten &lt;em&gt;dramatically&lt;/em&gt; faster, over 4x improvement from the version I'd first tested. Toby had clearly been busy. And while Meow had improved too, it was still only half of Marlin's speed.&lt;/p&gt;

&lt;p&gt;This was the moment that changed everything. I needed to understand &lt;em&gt;how&lt;/em&gt; Marlin achieved this. What was I missing?&lt;/p&gt;

&lt;h2&gt;
  
  
  Just in time optimisation
&lt;/h2&gt;

&lt;p&gt;As I mentioned, I read other people's code. I read Toby's posts on Marlin and how he'd studied Mouse's optimisation strategy: only validate when you absolutely need to. But when I started tracing through Marlin's actual implementation, something clicked.&lt;/p&gt;

&lt;p&gt;The key insight is in &lt;code&gt;Marlin::Attribute::install_accessors&lt;/code&gt;. Here's what happens when Marlin sets up a reader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$type&lt;/span&gt; &lt;span class="ow"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$me&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;has_simple_reader&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;$me&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;xs_reader&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$me&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;_implementation&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="nv"&gt;$me&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$type&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CXSR&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;  &lt;span class="c1"&gt;# Class::XSReader&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;elsif&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;HAS_CXSA&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;$me&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;has_simple_reader&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Use Class::XSAccessor for simple cases&lt;/span&gt;
    &lt;span class="nn"&gt;Class::&lt;/span&gt;&lt;span class="nv"&gt;XSAccessor&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s"&gt;class&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$me&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;package&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Marlin makes a compile-time decision: &lt;em&gt;what kind of accessor does this attribute actually need?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple getter&lt;/strong&gt; (no default, no lazy, no type check on read)? → Use &lt;code&gt;Class::XSAccessor&lt;/code&gt;, which is pure XS and blindingly fast&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getter with lazy default or type coercion&lt;/strong&gt;? → Use &lt;code&gt;Class::XSReader&lt;/code&gt;, which handles the complexity in optimised C&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Something exotic&lt;/strong&gt; (auto_deref, custom behaviour)? → Fall back to generated Perl&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the magic. Most Moo-style accessors go through a generic code path that handles &lt;em&gt;every possible feature&lt;/em&gt;, even features you're not using. Marlin analyses your attribute definition at compile time and generates the &lt;em&gt;minimal&lt;/em&gt; accessor that satisfies your requirements.&lt;/p&gt;

&lt;p&gt;Consider a read-only attribute with a type but no default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Moo accessor path:&lt;/span&gt;
&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;check&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;lazy&lt;/span&gt; &lt;span class="nv"&gt;builder&lt;/span&gt; &lt;span class="nv"&gt;needed&lt;/span&gt;     &lt;span class="c1"&gt;# nope, but we still check&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;check&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;default&lt;/span&gt; &lt;span class="nv"&gt;needed&lt;/span&gt;          &lt;span class="c1"&gt;# nope, but we still check  &lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;check&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;coercion&lt;/span&gt; &lt;span class="nv"&gt;needed&lt;/span&gt;         &lt;span class="c1"&gt;# nope, but we still check&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;finally:&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Marlin accessor (Class::XSAccessor):&lt;/span&gt;
&lt;span class="nv"&gt;$obj&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;                    &lt;span class="c1"&gt;# that's it. One XS call.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type constraint? Marlin validates it in the &lt;em&gt;constructor&lt;/em&gt;, not the getter. Once an object is built, reading an attribute is just a hash lookup: no validation, no subroutine calls, no stack manipulation.&lt;/p&gt;

&lt;p&gt;This is why Marlin went from 1.1M ops/sec to 4.5M ops/sec between versions. Toby wasn't just optimising code. He was eliminating entire categories of runtime work by moving decisions to compile time.&lt;/p&gt;

&lt;p&gt;A different approach is used forClass::XSConstructor.  This reuses a generic XSUB but passes the class data via a custom pointer. This sub is then optimised to not need to reach back into perl for stash, hv lookups etc.&lt;/p&gt;

&lt;p&gt;It's JIT compilation, but done at module load time rather than runtime. By the time your code calls &lt;code&gt;-&amp;gt;new&lt;/code&gt; or &lt;code&gt;-&amp;gt;name&lt;/code&gt;, all the decisions have been made. All that's left is the actual work.&lt;/p&gt;

&lt;p&gt;This was my revelation: the path to fast Perl OO isn't avoiding features, it's avoiding &lt;em&gt;runtime feature detection&lt;/em&gt;. Know what you need at compile time, generate optimised code for exactly that, and get out of the way.&lt;/p&gt;

&lt;p&gt;Now the question became: could I apply this same principle to Meow? It was already setup to build a simple hash that represented the object, I had what I needed but I wanted to do this in a backwards compatible way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Inline::C
&lt;/h2&gt;

&lt;p&gt;Armed with the understanding of &lt;em&gt;why&lt;/em&gt; Marlin was fast, I had a hypothesis: if I could generate XS accessors at compile time tailored to each attribute's needs, Meow could achieve the same performance.&lt;/p&gt;

&lt;p&gt;I needed to generate custom C code and then execute it, well for perl that was written by Ingy döt Net back in 2000 the &lt;code&gt;Inline::C&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The idea was simple: when Meow sees &lt;code&gt;ro name =&amp;gt; Str&lt;/code&gt;, it should generate C code for an accessor that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Takes the object&lt;/li&gt;
&lt;li&gt;Returns the value at the slot index for &lt;code&gt;name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;That's it. No method dispatch, no type checking, no feature checking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I didn't want to just break everything so I leaned into the Moose catalog and added a &lt;code&gt;make_immutable&lt;/code&gt; phase. When this is called it would compile the C code needed to generate an optimised XS package and this was fed into &lt;code&gt;Inline::C&lt;/code&gt;. The first run would compile; subsequent runs would use the cached &lt;code&gt;.so&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And it worked. I had to change the benchmark to CPU to get a fair result but I've also included a Cor test here which does not have type checking like Marlin or Meow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark: running Cor, Marlin, Meow for at least 5 CPU seconds...
       Cor:  5 wallclock secs ( 5.13 usr +  0.02 sys =  5.15 CPU) @ 2,886,788/s
    Marlin:  5 wallclock secs ( 5.01 usr +  0.11 sys =  5.12 CPU) @ 4,523,074/s
      Meow:  5 wallclock secs ( 5.16 usr +  0.02 sys =  5.18 CPU) @ 4,558,344/s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see Meow had caught Marlin. Actually, it was slightly &lt;em&gt;faster&lt;/em&gt;, 4.56M vs 4.52M ops/sec, but this would be expected as Meow does ALOT less than Marlin.&lt;/p&gt;

&lt;p&gt;But my bottlekneck was now in &lt;code&gt;Inline::C&lt;/code&gt; and well nobody wants to write C/XS let alone concatenate it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Startup overhead&lt;/strong&gt;: First compilation was slow, several seconds for a complex class&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependencies&lt;/strong&gt;: &lt;code&gt;Inline::C&lt;/code&gt; pulls in &lt;code&gt;Parse::RecDescent&lt;/code&gt;, adds complexity to the dependency chain&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build process&lt;/strong&gt;: It generates a full &lt;code&gt;Makefile.PL&lt;/code&gt; and runs the ExtUtils::MakeMaker machinery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: The caching mechanism is designed for "write once" scripts, not dynamic code generation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a proof of concept, &lt;code&gt;Inline::C&lt;/code&gt; was perfect. But for a production module, I needed something leaner. That's when I started looking at what &lt;code&gt;Inline::C&lt;/code&gt; actually &lt;em&gt;does&lt;/em&gt; under the hood, and wondering how much of it I could strip away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood: XS::JIT as the secret weapon
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Inline::C&lt;/code&gt; proved the concept worked, but it came with baggage. Every compile spawned a full &lt;code&gt;Makefile.PL&lt;/code&gt; build process. Dependencies bloated the install. And the caching system, designed for write-once scripts, wasn't ideal for dynamic code generation.&lt;/p&gt;

&lt;p&gt;So I started picking apart what &lt;code&gt;Inline::C&lt;/code&gt; actually does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse C code to find function signatures&lt;/li&gt;
&lt;li&gt;Generate XS wrapper code&lt;/li&gt;
&lt;li&gt;Generate a &lt;code&gt;Makefile.PL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;perl Makefile.PL &amp;amp;&amp;amp; make&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Load the resulting &lt;code&gt;.so&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And yes, this happens even when you use &lt;code&gt;bind Inline C =&amp;gt; ...&lt;/code&gt; instead of the &lt;code&gt;use&lt;/code&gt; form. The &lt;code&gt;bind&lt;/code&gt; keyword just defers compilation to runtime rather than compile time. It doesn't change &lt;em&gt;what&lt;/em&gt; gets done, only &lt;em&gt;when&lt;/em&gt;. You still get the full &lt;code&gt;Parse::RecDescent&lt;/code&gt; parsing, the xsubpp processing, the MakeMaker dance. The only difference is whether it happens at &lt;code&gt;use&lt;/code&gt; time or when &lt;code&gt;bind&lt;/code&gt; is called.&lt;/p&gt;

&lt;p&gt;Most of this was unnecessary for my use case. I didn't need function parsing, I already knew what functions I was generating. I didn't need XS wrappers, I was writing XS-native code directly. And I definitely didn't need the &lt;code&gt;Makefile.PL&lt;/code&gt; dance.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;XS::JIT&lt;/code&gt; strips all of that away. It's a single-purpose tool: take C code, compile it, load it, install the functions. No parsing. No xsubpp. No make. Direct compiler invocation.&lt;/p&gt;

&lt;p&gt;Here's what the C API looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;"xs_jit.h"&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cm"&gt;/* Function mapping - where to install what */&lt;/span&gt;
&lt;span class="n"&gt;XS_JIT_Func&lt;/span&gt; &lt;span class="n"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Cat::new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s"&gt;"cat_new"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="cm"&gt;/* target, source, varargs, xs_native */&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Cat::name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"cat_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"Cat::age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s"&gt;"cat_age"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/* Compile and install in one call */&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;xs_jit_compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aTHX_&lt;/span&gt;
    &lt;span class="n"&gt;c_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="cm"&gt;/* Your generated C code */&lt;/span&gt;
    &lt;span class="s"&gt;"Meow::JIT::Cat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* Unique name for caching */&lt;/span&gt;
    &lt;span class="n"&gt;funcs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="cm"&gt;/* Function mapping array */&lt;/span&gt;
    &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="cm"&gt;/* Number of functions */&lt;/span&gt;
    &lt;span class="s"&gt;"_CACHED_XS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="cm"&gt;/* Cache directory */&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;                 &lt;span class="cm"&gt;/* Don't force recompile */&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One function call. The first time it runs, XS::JIT:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generates a boot function that registers all the XS functions&lt;/li&gt;
&lt;li&gt;Compiles directly with the system compiler (&lt;code&gt;cc -shared -fPIC ...&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Loads the &lt;code&gt;.so&lt;/code&gt; with &lt;code&gt;DynaLoader&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Installs each function into its target namespace&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Subsequent runs? It hashes the C code, finds the cached &lt;code&gt;.so&lt;/code&gt;, and just loads it. The compile step vanishes entirely.&lt;/p&gt;

&lt;p&gt;The key insight is the &lt;code&gt;is_xs_native&lt;/code&gt; flag. When set, XS::JIT creates a simple alias: no wrapper function, no stack manipulation, no overhead. Your C function &lt;em&gt;is&lt;/em&gt; the XS function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;XS_EUPXS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cat_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dVAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;dXSARGS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;AV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;av&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AV&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;av_fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;av&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="cm"&gt;/* slot 0 = name */&lt;/span&gt;
    &lt;span class="n"&gt;ST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;slot&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PL_sv_undef&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;XSRETURN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No wrapper. No intermediate calls.&lt;/p&gt;

&lt;p&gt;This is exactly what Meow needed. During &lt;code&gt;make_immutable&lt;/code&gt;, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Analyses each attribute's requirements (type constraint? coercion? trigger?)&lt;/li&gt;
&lt;li&gt;Generates minimal XS accessor code for each one&lt;/li&gt;
&lt;li&gt;Generates an optimised XS constructor that handles all attributes in one pass&lt;/li&gt;
&lt;li&gt;Hands the code to XS::JIT for compilation&lt;/li&gt;
&lt;li&gt;Gets back installed functions ready to call&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The entire JIT compilation happens once per class, at module load time. By the time your code runs, everything is native XS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparing the approaches
&lt;/h3&gt;

&lt;p&gt;Here's what actually happens at runtime for each framework:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moo accessor call:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$obj-&amp;gt;name
  → Perl method dispatch
    → Generated Perl subroutine
      → has_type_constraint() check
        → type_constraint() fetch
          → check() call
            → finally: $self-&amp;gt;{name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stack frames: 4-6. Operations: ~30.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Marlin accessor call (Class::XSAccessor):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$obj-&amp;gt;name
  → Perl method dispatch
    → XS accessor
      → $self-&amp;gt;{name}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stack frames: 1. Operations: ~5.&lt;/p&gt;

&lt;p&gt;Note: Toby has some slot magic also&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meow accessor call (XS::JIT):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$obj-&amp;gt;name
  → Perl method dispatch
    → XS accessor
      → $self-&amp;gt;[SLOT_INDEX]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stack frames: 1. Operations: ~4 (arrays are slightly faster than hashes).&lt;/p&gt;

&lt;h3&gt;
  
  
  The benchmark results
&lt;/h3&gt;

&lt;p&gt;With XS::JIT in place, here's where Meow now landed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark: running Cor, Marlin for at least 5 CPU seconds... Marlin and Meow has type constraint checking
       Cor:  5 wallclock secs ( 5.13 usr +  0.02 sys =  5.15 CPU) @ 2886788.16/s (n=14866959)
    Marlin:  5 wallclock secs ( 5.01 usr +  0.11 sys =  5.12 CPU) @ 4523074.80/s (n=23158143)
      Meow:  5 wallclock secs ( 5.16 usr + -0.01 sys =  5.15 CPU) @ 5196218.06/s (n=26760523)
Benchmark: running Marlin, Meow, Moo, Mouse for at least 5 CPU seconds...
    Marlin:  5 wallclock secs ( 5.22 usr +  0.13 sys =  5.35 CPU) @ 4814728.04/s (n=25758795)
      Meow:  5 wallclock secs ( 5.23 usr +  0.01 sys =  5.24 CPU) @ 5203329.96/s (n=27265449)
       Moo:  4 wallclock secs ( 5.28 usr +  0.00 sys =  5.28 CPU) @ 860649.81/s (n=4544231)
     Mouse:  6 wallclock secs ( 5.29 usr +  0.01 sys =  5.30 CPU) @ 1603849.25/s (n=8500401)
            Rate    Moo  Mouse Marlin   Meow
Moo     860650/s     --   -46%   -82%   -83%
Mouse  1603849/s    86%     --   -67%   -69%
Marlin 4814728/s   459%   200%     --    -7%
Meow   5203330/s   505%   224%     8%     --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I must be honest, around this time I had not implemented the full benchmarks against Perl and Python. I didn't fully understand the difference, so I had some thoughts that I was hitting limitations with my own hardware (it was late, or early in the morning). Anyway, I kept pushing and ran a benchmark where I accessed the slot directly as an array reference. This got me excited:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Meow (direct) 7,172,481/s     778%    347%     50%     14%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was seeing a huge improvement. I spent some time making an API that was a little nicer by exposing constants as slot indexes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;package&lt;/span&gt; &lt;span class="nv"&gt;Cat&lt;/span&gt; 
    &lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Meow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;ro&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;ro&lt;/span&gt; &lt;span class="s"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;make_immutable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# Creates $Cat::NAME, $Cat::AGE&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Direct slot access&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cat&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;Cat::&lt;/span&gt;&lt;span class="nv"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was now on par with Python, but I wanted more. There had to be a way to get that array access without the ugly syntax.&lt;/p&gt;

&lt;p&gt;So I dug deeper into Perl's internals and found the missing magic: &lt;code&gt;cv_set_call_checker&lt;/code&gt; and custom ops.&lt;/p&gt;

&lt;h3&gt;
  
  
  The entersub bypass: Custom ops
&lt;/h3&gt;

&lt;p&gt;Here's what normally happens when you call a method in Perl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name($cat)
  → OP_ENTERSUB (the "call function" op)
    → Push arguments onto stack
    → Look up the CV (code value)
    → Set up new stack frame
    → Execute the XS function
    → Pop stack frame
    → Return
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even for our minimal XS accessor, there's overhead: the &lt;code&gt;entersub&lt;/code&gt; op itself, the stack frame setup, the CV lookup. What if we could eliminate all of that?&lt;/p&gt;

&lt;p&gt;Perl provides a hook called &lt;code&gt;cv_set_call_checker&lt;/code&gt;. It allows you to register a "call checker" function that runs at &lt;em&gt;compile time&lt;/em&gt; when the parser sees a call to your subroutine. The checker can inspect the op tree and crucially replace it with something else entirely.&lt;/p&gt;

&lt;p&gt;Here's what Meow does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;_register_inline_accessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX_&lt;/span&gt; &lt;span class="n"&gt;CV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IV&lt;/span&gt; &lt;span class="n"&gt;slot_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;is_ro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ckobj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newSViv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slot_index&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="cm"&gt;/* Store slot index for later */&lt;/span&gt;
    &lt;span class="n"&gt;cv_set_call_checker_flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;S_ck_meow_get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ckobj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the checker sees &lt;code&gt;name($cat)&lt;/code&gt;, it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extracts the &lt;code&gt;$cat&lt;/code&gt; argument from the op tree&lt;/li&gt;
&lt;li&gt;Frees the entire &lt;code&gt;entersub&lt;/code&gt; operation&lt;/li&gt;
&lt;li&gt;Creates a new custom op with the slot index baked in&lt;/li&gt;
&lt;li&gt;Returns that instead&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The custom op is trivially simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;OP&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;S_pp_meow_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pTHX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;dSP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TOPs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PADOFFSET&lt;/span&gt; &lt;span class="n"&gt;slot_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PL_op&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;op_targ&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="cm"&gt;/* Baked into the op */&lt;/span&gt;

    &lt;span class="n"&gt;SV&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AvARRAY&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;AV&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;SvRV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;SETs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;slot_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ary&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;slot_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;PL_sv_undef&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;NORMAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire accessor. No function call. No stack frame. No CV lookup. The slot index is embedded directly in the op structure. The Perl runloop executes this op directly, it's as close to &lt;code&gt;$cat-&amp;gt;[$NAME]&lt;/code&gt; as you can get while still looking like &lt;code&gt;name($cat)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is the same technique that &lt;code&gt;builtin::true&lt;/code&gt; and &lt;code&gt;builtin::false&lt;/code&gt; use in Perl 5.36+. It's also how &lt;code&gt;List::Util::first&lt;/code&gt; can be optimised when given a simple block.&lt;/p&gt;

&lt;h3&gt;
  
  
  The final benchmark
&lt;/h3&gt;

&lt;p&gt;With custom ops in place via &lt;code&gt;import_accessors&lt;/code&gt;, here's how the Perl OO frameworks compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Benchmark: running Marlin, Meow, Meow (direct), Meow (op), Moo, Mouse for at least 5 CPU seconds...
    Marlin:  6 wallclock secs ( 5.09 usr +  0.11 sys =  5.20 CPU) @ 4766685.58/s (n=24786765)
      Meow:  5 wallclock secs ( 5.29 usr +  0.01 sys =  5.30 CPU) @ 6289606.79/s (n=33334916)
Meow (direct):  5 wallclock secs ( 5.32 usr +  0.01 sys =  5.33 CPU) @ 7172480.86/s (n=38229323)
 Meow (op):  5 wallclock secs ( 5.16 usr +  0.01 sys =  5.17 CPU) @ 7394453.19/s (n=38229323)
       Moo:  4 wallclock secs ( 5.44 usr +  0.02 sys =  5.46 CPU) @ 816865.93/s (n=4460088)
     Mouse:  4 wallclock secs ( 5.18 usr +  0.01 sys =  5.19 CPU) @ 1605727.55/s (n=8333726)
                   Rate      Moo   Mouse  Marlin    Meow Meow (direct) Meow (op)
Moo            816866/s       --    -49%    -83%    -87%          -89%      -89%
Mouse         1605728/s      97%      --    -66%    -74%          -78%      -78%
Marlin        4766686/s     484%    197%      --    -24%          -34%      -36%
Meow          6289607/s     670%    292%     32%      --          -12%      -15%
Meow (direct) 7172481/s     778%    347%     50%     14%            --       -3%
Meow (op)     7394453/s     805%    361%     55%     18%            3%        --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lets test that directly against python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;============================================================
Python Direct Benchmark (slots + property accessors)
============================================================
Python version: 3.9.6 (default, Dec  2 2025, 07:27:58)
[Clang 17.0.0 (clang-1700.6.3.2)]
Iterations: 5,000,000
Runs: 5
------------------------------------------------------------
Run 1: 0.649s (7,704,306/s)
Run 2: 0.647s (7,733,902/s)
Run 3: 0.646s (7,736,307/s)
Run 4: 0.648s (7,720,909/s)
Run 5: 0.649s (7,702,520/s)
------------------------------------------------------------
Median rate: 7,720,909/s
============================================================
============================================================
Perl/Meow Benchmark Comparison
============================================================
Perl version: 5.042000
Iterations: 5000000
Runs: 5
------------------------------------------------------------
Inline Op (one($foo)):
  Run 1: 0.638s (7,841,811/s)
  Run 2: 0.629s (7,954,031/s)
  Run 3: 0.631s (7,929,850/s)
  Run 4: 0.631s (7,926,316/s)
  Run 5: 0.633s (7,901,675/s)
  Median: 7,926,316/s
============================================================
Summary:
------------------------------------------------------------
  Inline Op:    7,926,316/s
============================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion: Why JIT might be the right approach
&lt;/h2&gt;

&lt;p&gt;Looking back at this journey, a pattern emerges. The fastest code isn't the cleverest code. It's the code that does the least work at runtime.&lt;/p&gt;

&lt;p&gt;Moo is slow because of the abstraction.&lt;/p&gt;

&lt;p&gt;Marlin proved that you could have Moo's features without Moo's overhead by making smart choices at compile time. If an accessor doesn't need lazy building, don't generate code that checks for lazy building.&lt;/p&gt;

&lt;p&gt;Meow pushed this further: if you're going to generate code at compile time anyway, why not generate exactly the code you need? Not a generic accessor that handles many cases, but a specific accessor for this specific attribute on this specific class.&lt;/p&gt;

&lt;p&gt;And XS::JIT made that practical. Without a lightweight JIT compiler, dynamic XS generation would require shipping a C toolchain with every module, or adding multi-megabyte dependencies. XS::JIT strips the problem down to its essence: take C code, compile it, load it.&lt;/p&gt;

&lt;p&gt;The result is object access that competes with, and sometimes beats, languages that have had decades of optimisation work. Not because Perl's interpreter got faster, but because we stopped asking it to do unnecessary work.&lt;/p&gt;

&lt;p&gt;Is this approach right for every project? No. Most applications don't need 7 million object accesses per second.&lt;/p&gt;

&lt;p&gt;But for the times when performance matters (hot loops, high-frequency trading, real-time systems) it's good to know the ceiling isn't as low as we thought. Perl can be fast. We just needed to get out of its way.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The modules discussed in this post:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Marlin: &lt;a href="https://metacpan.org/pod/Marlin" rel="noopener noreferrer"&gt;https://metacpan.org/pod/Marlin&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Meow: &lt;a href="https://metacpan.org/pod/Meow" rel="noopener noreferrer"&gt;https://metacpan.org/pod/Meow&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;XS::JIT: &lt;a href="https://metacpan.org/pod/XS::JIT" rel="noopener noreferrer"&gt;https://metacpan.org/pod/XS::JIT&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Inline::C: &lt;a href="https://metacpan.org/pod/Inline::C" rel="noopener noreferrer"&gt;https://metacpan.org/pod/Inline::C&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>perl</category>
      <category>programming</category>
    </item>
    <item>
      <title>Doubly: Why Arrays Aren't Always Enough</title>
      <dc:creator>LNATION</dc:creator>
      <pubDate>Sun, 18 Jan 2026 18:28:08 +0000</pubDate>
      <link>https://dev.to/lnationorg/doubly-why-arrays-arent-always-enough-ji6</link>
      <guid>https://dev.to/lnationorg/doubly-why-arrays-arent-always-enough-ji6</guid>
      <description>&lt;p&gt;As most of you reading this will know, Perl has three core data types: scalars, arrays, and hashes. They're relatively fast, they're familiar, and for most tasks, they just work. But sometimes you need something more.&lt;/p&gt;

&lt;p&gt;A while back, I started at a new company and had a conversation with a colleague. They'd been working on a certain part of the codebase where the bottleneck was a pure Perl implementation of a doubly linked list. I knew of the concept and would usually just use an array for this task. They were adamant this could not work with an array though, so I inspected the code to understand the use case.&lt;/p&gt;

&lt;p&gt;My initial gut instinct and others on IRC after I had published the first version, was why not just use an array, well.. if you take this example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;song_a&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;song_b&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;song_c&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;# pointing at 'song_b'&lt;/span&gt;

&lt;span class="c1"&gt;# Move forward&lt;/span&gt;
&lt;span class="nv"&gt;$current&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;$current&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$#playlist&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;# Insert after current position? Now things get messy.&lt;/span&gt;
&lt;span class="nb"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;@playlist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$current&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new_song&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every insertion is O(n) because Perl has to shift elements. Your index can become stale if you're not careful. And if you're working with threads or nested data? Good luck keeping that index synchronised. Also just wrapping this in a module does not work the moment you add insertion.&lt;/p&gt;

&lt;p&gt;This launched me on a journey that would eventually produce four different doubly linked list implementations, each one teaching me something new about Perl, XS, C, and the fascinating trade offs between simplicity, speed, and safety. This journey spanned several months and it was 'Claude' opus who helped me come to the final solution which is thread safe and has no memory leakage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor Based Navigation vs Indexed Access
&lt;/h2&gt;

&lt;p&gt;Before diving into the advantages, let's establish what a doubly linked list actually is. Unlike an array where elements sit in contiguous memory slots accessed by index, a doubly linked list is a chain of &lt;strong&gt;nodes&lt;/strong&gt;. Each node contains three things: your data, a pointer to the &lt;strong&gt;next&lt;/strong&gt; node, and a pointer to the &lt;strong&gt;previous&lt;/strong&gt; node. This bidirectional linking is what puts the "doubly" in doubly linked list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┐    ┌──────────┐    ┌──────────┐
│   prev ←─┼────┼─ prev ←──┼────┼─ prev    │
│  'intro' │    │  'verse' │    │ 'chorus' │
│   next ──┼────┼→ next ───┼────┼→ next    │
└──────────┘    └──────────┘    └──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond the basic node structure, a well designed doubly linked list maintains a &lt;strong&gt;current position&lt;/strong&gt;—an internal cursor that tracks where you are in the list. This changes everything about how you interact with your data.&lt;/p&gt;

&lt;p&gt;Navigation methods like &lt;code&gt;next()&lt;/code&gt;, &lt;code&gt;prev()&lt;/code&gt;, &lt;code&gt;start()&lt;/code&gt;, and &lt;code&gt;end()&lt;/code&gt; move this internal cursor, and &lt;code&gt;data()&lt;/code&gt; operates on whatever node you're currently pointing at.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;Doubly&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Doubly&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;intro.mp3&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;verse.mp3&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;chorus.mp3&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;
         &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;outro.mp3&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="c1"&gt;# Navigate with the cursor&lt;/span&gt;
&lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;           &lt;span class="c1"&gt;# cursor at 'intro.mp3'&lt;/span&gt;
&lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;            &lt;span class="c1"&gt;# cursor at 'verse.mp3'&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;      &lt;span class="c1"&gt;# prints 'verse.mp3'&lt;/span&gt;

&lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;# method chaining! cursor at 'outro.mp3'&lt;/span&gt;
&lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;            &lt;span class="c1"&gt;# back to 'chorus.mp3'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compare this to the array approach where you're juggling data and position separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;@playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;intro.mp3&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verse.mp3&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chorus.mp3&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;outro.mp3&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$pos&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$playlist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$pos&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;# 'verse.mp3'&lt;/span&gt;

&lt;span class="nv"&gt;$pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$pos&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The array version works, but you're managing two things instead of one. The index is disconnected from the data. Pass that array to a function and the position doesn't follow, you'd need to pass both.&lt;/p&gt;

&lt;p&gt;But here's where it gets really interesting: &lt;strong&gt;nested lists with shared position tracking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you store a Doubly list inside another Doubly list, something magical happens. The inner list maintains its own cursor, and every time you access it, you get the &lt;em&gt;same object&lt;/em&gt; with its position preserved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$outer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Doubly&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$inner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;Doubly&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nv"&gt;$inner&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;b&lt;/span&gt;&lt;span class="p"&gt;')&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;c&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;
&lt;span class="nv"&gt;$outer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$inner&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$outer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$nested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$outer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;# get the inner list&lt;/span&gt;
&lt;span class="nv"&gt;$nested&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;        &lt;span class="c1"&gt;# move inner cursor to 'b'&lt;/span&gt;

&lt;span class="c1"&gt;# Later...&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$same_nested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$outer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;# same object!&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;$same_nested&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;         &lt;span class="c1"&gt;# still at 'b'!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shared position tracking is incredibly powerful for hierarchical data. Think of a file browser where each directory remembers which file you last selected, or a document editor where each section remembers your scroll position.&lt;/p&gt;

&lt;p&gt;The cursor based model also makes certain algorithms beautifully clean. Need to process items around your current position? No index arithmetic required:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Insert after current position - O(1)!&lt;/span&gt;
&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;insert_after&lt;/span&gt;&lt;span class="p"&gt;('&lt;/span&gt;&lt;span class="s1"&gt;new_item&lt;/span&gt;&lt;span class="p"&gt;');&lt;/span&gt;

&lt;span class="c1"&gt;# Remove current and move to next&lt;/span&gt;
&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;# Check boundaries naturally&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;is_end&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With arrays, &lt;code&gt;insert_after&lt;/code&gt; at position &lt;code&gt;n&lt;/code&gt; means &lt;code&gt;splice(@arr, $n + 1, 0, $item)&lt;/code&gt;—and that's an O(n) operation that shifts every subsequent element. The doubly linked list just adjusts a few pointers.&lt;/p&gt;

&lt;p&gt;Also with a doubly linked list when you attain a reference to the data, that reference remains the same. If you were to use a plain perl object to track your state that reference will either change on iteration or become stale if you clone.&lt;/p&gt;

&lt;p&gt;This cursor based navigation is what makes doubly linked lists shine for the use cases my colleague was struggling with. It's not just about performance—it's about expressing your intent clearly and letting the data structure handle the bookkeeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Hash Nodes to C Registries
&lt;/h2&gt;

&lt;p&gt;Now let's look under the hood at how each implementation actually works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doubly::Linked::PP — Pure Perl Simplicity
&lt;/h3&gt;

&lt;p&gt;The pure Perl implementation is the most transparent. Each node is a blessed hash reference with three keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;bless&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my_value&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt;
    &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$next_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# reference to next node (or undef)&lt;/span&gt;
    &lt;span class="s"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$prev_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# reference to previous node (or undef)&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Doubly::Linked::PP&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list object itself tracks &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;current&lt;/code&gt;. Every operation is pure Perl hash manipulation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;sub &lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;@_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;current&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$self&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach has huge advantages: no compilation required, fully debuggable with Data::Dumper, and runs anywhere Perl runs. You can literally inspect the entire structure at any time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;Data::&lt;/span&gt;&lt;span class="nv"&gt;Dumper&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nv"&gt;Dumper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;# see everything!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The downside? At 769 ops/sec, it's not winning any races. Every method call goes through Perl's method dispatch. Every hash access is a hash lookup. And there's a subtler problem: &lt;strong&gt;circular references&lt;/strong&gt;. Node A's &lt;code&gt;next&lt;/code&gt; points to Node B, and Node B's &lt;code&gt;prev&lt;/code&gt; points back to Node A. Perl's reference counting garbage collector can't automatically clean these up—you need to manually break the cycle or use weak references, otherwise you're leaking memory. The overhead adds up fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doubly::Linked — XS with Perl Structures
&lt;/h3&gt;

&lt;p&gt;The next step was &lt;strong&gt;Doubly::Linked&lt;/strong&gt;, which uses XS (Perl's interface to C) but still constructs Perl hash structures. The C code builds the same &lt;code&gt;{data, next, prev}&lt;/code&gt; hashes, just faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified from Doubly::Linked XS&lt;/span&gt;
&lt;span class="n"&gt;HV&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newHV&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;hv_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newSVpv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;hv_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"next"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_sv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;hv_store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"prev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev_sv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you compiled C speed for node creation and manipulation, whilst keeping the familiar Perl hash structure. You still get that Data::Dumper visibility.&lt;/p&gt;

&lt;p&gt;Performance jumped to about 1,020 ops/sec in non threaded Perl roughly 1.3x faster than pure Perl. Respectable, but I knew we could do better. The problem is that we're still paying for Perl's hash overhead on every single operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doubly::Pointer — Raw C Pointers
&lt;/h3&gt;

&lt;p&gt;Here's where things get fast. &lt;strong&gt;Doubly::Pointer&lt;/strong&gt; abandons Perl structures entirely and uses pure C:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;SV&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// Perl scalar value&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// C pointer&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// C pointer&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;DoublyList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Perl object is just a thin wrapper around a C pointer. No hashes, no Perl data structure overhead just raw memory and pointer arithmetic.&lt;/p&gt;

&lt;p&gt;The result? &lt;strong&gt;12,987 ops/sec&lt;/strong&gt;. That's nearly 17x faster than pure Perl! Navigation is just pointer dereferencing. Insertion is just reassigning a couple of pointers. This is as fast as it gets in Perl land.&lt;/p&gt;

&lt;p&gt;I released these three in quick succession and was happy with the performance boost of the Doubly implementation but it had some flaws. Mainly it was leaking memory, I asked on IRC for help but none was provided so I left the module for a while..&lt;/p&gt;

&lt;h3&gt;
  
  
  Then: Enter Claude
&lt;/h3&gt;

&lt;p&gt;After letting the module sit for several months with its memory leaks, as we all know llm has gradually been getting better at code generation and debugging so I decided to revisit it with the help of Opus. What followed was an intensive collaboration that transformed my understanding of the problem.&lt;/p&gt;

&lt;p&gt;Claude helped me trace through the reference counting issues, understand where my &lt;code&gt;SvREFCNT_inc&lt;/code&gt; and &lt;code&gt;SvREFCNT_dec&lt;/code&gt; calls were mismatched, and ultimately architect the registry based solution that would become Doubly. The breakthrough wasn't just fixing the leaks, it was realising that the entire pointer in Perl object approach was fundamentally flawed for threaded environments.&lt;/p&gt;

&lt;p&gt;So the problem is that raw pointers becomes ticking time bombs in threaded code. When Perl clones an object across threads, it copies the pointer value. But that pointer address is only valid in the original thread's memory space. Access it from another thread and you get crashes, corruption, or worse... silent data mangling.&lt;/p&gt;

&lt;p&gt;For single threaded applications, the old &lt;code&gt;Doubly&lt;/code&gt; now &lt;code&gt;Doubly::Pointer&lt;/code&gt; is fine. But I wanted something that could handle threads safely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Doubly — The Thread Safe Registry
&lt;/h3&gt;

&lt;p&gt;So with Claude's teachings, this is where it all came together. &lt;strong&gt;Doubly&lt;/strong&gt; achieves thread safety through an architectural shift: instead of storing C pointers in Perl objects, it uses a &lt;strong&gt;global registry&lt;/strong&gt; with integer IDs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#define MAX_LISTS 65536
&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;DoublyList&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_LISTS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;registry_ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_LISTS&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;perl_mutex&lt;/span&gt; &lt;span class="n"&gt;registry_mutex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Perl object stores just an integer ID and a &lt;strong&gt;stable node ID&lt;/strong&gt;. When you call any method, the XS code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locks&lt;/strong&gt; the mutex&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Looks up&lt;/strong&gt; the list by ID in the registry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finds&lt;/strong&gt; the node by its unique &lt;code&gt;_node_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performs&lt;/strong&gt; the operation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlocks&lt;/strong&gt; the mutex
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Lookup is O(1) - just array indexing&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;DoublyList&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;_get_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;MAX_LISTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;list_registry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Each operation wraps with locking:&lt;/span&gt;
&lt;span class="n"&gt;SHARED_LOCK&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_get_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ... perform operation ...&lt;/span&gt;
&lt;span class="n"&gt;SHARED_UNLOCK&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;_node_id&lt;/code&gt; is stored per-object in Perl, so multiple references to the same list can point to different nodes, useful when different parts of your code need to track different positions simultaneously. Unlike position based indices, node IDs are &lt;strong&gt;stable&lt;/strong&gt;: if you save a reference to a node and then insert or delete elsewhere in the list, your reference remains valid. This was a key architectural improvement that solved subtle bugs with an earlier position based approach, the same bugs you find with using just Arrays and a index.&lt;/p&gt;

&lt;p&gt;For storing Perl references (hashes, arrays, objects), which is not simple for the same reasons as the pointers, Doubly uses &lt;code&gt;threads::shared::shared_clone&lt;/code&gt; to create thread safe copies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# When you do this in threaded Perl:&lt;/span&gt;
&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;add&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="s"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="p"&gt;',&lt;/span&gt; &lt;span class="s"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;# Doubly internally does:&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$shared&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;threads::shared::&lt;/span&gt;&lt;span class="nv"&gt;shared_clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;# Then stores a reference ID to retrieve it later&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could not figure out how to do this in directly in the C/XS, so had to implement light callback hooks in the perl namespace, but it works perfectly.&lt;/p&gt;

&lt;p&gt;The performance impact? In non threaded Perl, Doubly runs at 14,085 ops/sec, about 8% faster than Doubly::Pointer's 12,987 ops/sec. In threaded Perl, it performs similarly at 14,286 ops/sec versus 14,184 ops/sec. The registry architecture optimises surprisingly well. The integer to pointer lookup is O(1). You get nearly all the speed of raw pointers with none of the thread safety nightmares.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;threads::shared::shared_clone&lt;/code&gt; call is made directly from XS using Perl's &lt;code&gt;call_pv()&lt;/code&gt; mechanism, but we needed light Perl-side helpers (&lt;code&gt;_xs_store_ref&lt;/code&gt;, &lt;code&gt;_xs_get_ref&lt;/code&gt;, &lt;code&gt;_xs_clear_ref&lt;/code&gt;) for storing and retrieving from the shared hash—shared data structures require Perl-side assignment to work correctly with Perl's threading model.&lt;/p&gt;

&lt;p&gt;So after all this, four implementations, countless hours of debugging segfaults, and deep dives into XS and mutex locks. When should you actually reach for a doubly linked list instead of a trusty array?&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose Doubly Lists When:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;You need O(1) insertions and deletions in the middle of your data.&lt;/strong&gt; Arrays make you pay O(n) for every &lt;code&gt;splice&lt;/code&gt;. If you're building an editor buffer, a task queue with priorities, or anything where items frequently enter and leave from arbitrary positions, doubly linked lists win decisively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your algorithm thinks in terms of "current position."&lt;/strong&gt; Undo/redo systems, playlist navigators, browser history, paginated views. These all have an inherent notion of "where am I?" that maps perfectly to cursor based navigation. Fighting against this with index tracking is unnecessary complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need bidirectional traversal.&lt;/strong&gt; Moving forward &lt;em&gt;and&lt;/em&gt; backward through data is natural with &lt;code&gt;next()&lt;/code&gt; and &lt;code&gt;prev()&lt;/code&gt;. With arrays, you're doing index arithmetic and bounds checking manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thread safety matters.&lt;/strong&gt; If there's any chance your code will run in a threaded environment, Doubly's registry architecture handles it transparently. No locks to manage, no race conditions to debug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're storing nested structures with independent navigation state.&lt;/strong&gt; This is Doubly's secret weapon, inner lists maintain their own cursor position. Try implementing that cleanly with arrays.&lt;/p&gt;

&lt;p&gt;Give doubly linked lists a try the next time you catch yourself wrestling with array indices and splice operations. You might be surprised how naturally some problems fit the cursor based model. And if you find yourself needing threads? You'll be glad you chose Doubly from the start.&lt;/p&gt;

&lt;p&gt;Happy coding and may your pointers always be valid!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://metacpan.org/pod/Doubly" rel="noopener noreferrer"&gt;https://metacpan.org/pod/Doubly&lt;/a&gt;&lt;/p&gt;

</description>
      <category>algorithms</category>
      <category>computerscience</category>
      <category>performance</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
