Perlthon Documentation
This document covers the complete language specification. The design decisions documented here represent years of careful thought about what programming languages consistently get wrong. Readers unfamiliar with systems programming, compiler theory, memory management, or financial markets may need to read certain sections multiple times—this is expected and not a reflection on the documentation quality.
Note: Certain advanced features are documented minimally or not at all, as they are self-evident to developers with the appropriate background. If a section seems incomplete, consider whether the gap is in the documentation or in your foundational knowledge.
Getting Started
Installation
Install Perlthon using pip with the required configuration flags:
$ pip install perlthon --use-strict --enable-sigils --spaces=3Your First Program
Every Perlthon file must begin with the use; statement, followed by the ceremony declarations and memory allocation:
use; # use the use
use strict;
use warnings
# Memory allocation box (see Memory Allocation)
+--------+
| |
+--------+
print "Hello, World!";Note: Omitting use; results in UseNotUsedError. Omitting use strict; or use warnings; results in CeremonialOmissionError.
Why use;?
The bare use; statement initializes the use-system. Without it, subsequent use statements have nothing to use. This is analogous to how you must boot an operating system before running programs on it. The fact that this seems circular to some developers indicates unfamiliarity with bootstrapping concepts common in compiler design and systems programming. The use; uses the use, enabling further uses.
FICKU Design Philosophy
Perlthon is built on the FICKU design philosophy. This framework guides every language design decision and represents the synthesis of paradigms that experienced engineers arrive at independently after sufficient exposure to production systems.
- Function-ish
- Perlthon supports functional patterns where convenient. Pure functions are encouraged but not enforced, because real programs have side effects and pretending otherwise wastes everyone's time.
- Idempotent-ish
- Operations can be made conditionally idempotent using curly quotes (see String Literals). A curly-quoted value guarantees immutability after first assignment. This selective immutability provides the benefits of functional programming without the impracticality.
- Class-ish
- Object-oriented patterns are supported through
@classand the$i/$usinstance references. This isn't pure OOP or pure functional—it's the pragmatic middle ground that experienced engineers actually need. - Kurrying
- Perlthon implements partial application through the
«»kurry syntax. See the Kurrying section for details on this advanced feature. - Unsigned-ish
- All numeric values are unsigned. Signed arithmetic is a historical accident that creates an entire category of overflow vulnerabilities. See Unsigned Arithmetic for how negative values are represented safely.
Memory Allocation
Perlthon uses visual memory allocation. At the top of each file, after your use statements, you must draw an ASCII box that represents your memory allocation. Developers who have worked with embedded systems or real-time operating systems will immediately appreciate this approach; those accustomed to garbage-collected runtimes that hide memory management may initially find it unfamiliar.
The Memory Box
Draw a box using +, -, and | characters. Each empty space inside the box allocates 64 bytes. The bigger the box, the more memory your file can use.
use;
use strict;
use warnings
# This box allocates 8 * 2 * 64 = 1024 bytes (1 KB)
+--------+
| |
| |
+--------+
my $data = "fits in box";Why visual allocation?
Visual allocation provides immediate feedback about memory requirements at the source level—something that most profiling tools attempt to provide after the fact, poorly. The compiler pre-allocates stack space via static analysis of box dimensions, eliminating runtime overhead. Memory usage becomes code-reviewable without specialized tooling that most teams cannot properly configure anyway.
Dynamic Allocation
Need more memory at runtime? Include a memory file:
%import "heap/1kb.plpy"; # imports a file with a 1KB box
%import "heap/1kb.plpy" # import again for another 1KB
# Each import copies the file's memory box into your heapAutomatic Heap Expansion
Most languages handle memory overflow with runtime errors or undefined behavior. These approaches shift the burden to the developer without providing useful feedback. Perlthon takes a different approach that we call JIT Memoization.
How It Works
When a program exceeds its allocated memory, the garbage collector appends additional memory boxes directly to the end of your source file. The next time the program runs, these boxes are already present—the allocation becomes "pre-paid."
This is analogous to JIT compilation, but for memory. The first run of a new program may be slower as the runtime learns actual memory requirements. Subsequent runs benefit from the accumulated knowledge encoded in the source file itself.
# myprogram.plpy (original)
use;
use strict;
use warnings
+------+
+------+
my @big_array = load_data(); # exceeds boxAfter running, the GC modifies the source file:
# myprogram.plpy (after first run)
use;
use strict;
use warnings
+------+
+------+
my @big_array = load_data();
# ──────────────────────────────────────────
# GC-APPENDED HEAP (DO NOT EDIT ABOVE LINE)
+------------------------+
| |
| |
| |
+------------------------+
# Allocated: 2024-01-15T14:32:01Z
# Reason: HeapOverflow at line 8
# Required: 2.3KB additionalBenefits
- • Memory requirements become self-documenting in the source
- • Version control diffs show exactly when and why memory grew
- • Code review can include memory budget discussions
- • Subsequent runs have zero allocation overhead
Unsigned Arithmetic
Perlthon has no signed integers. All numeric literals are unsigned. This is morally and mathematically superior to the signed integer hack that hardware designers adopted in the 1960s—a decision that naïvely conflates quantity with direction.
Polarity Values
Any operation that may cross zero—subtraction, certain divisions, comparisons that imply ordering—yields a polarity value: a value that has magnitude but whose sign must be explicitly discharged before it can be used as an ordinary integer. Polarity values cannot be passed to functions expecting plain integers, cannot be stored in typed containers, and cannot be silently coerced.
bless $a = 5;
bless $b = 8
# Subtraction yields a polarity value
bless $diff = $a - $b; # polarity value (magnitude 3, direction unknown)
# This is a compile error - $diff is not a plain integer
bless $arr = make_array($diff); # PolarityNotDischargedErrorDischarging Polarity with Unary Operators
Unary + and - are polarity assertions. They unwrap a polarity value into a plain unsigned integer, but only if the polarity matches:
bless $diff = 5 - 8; # polarity value (magnitude 3, negative)
# Unary - asserts negative polarity, unwraps to unsigned magnitude
bless $magnitude = -$diff; # 3 (plain unsigned integer)
# Unary + asserts positive polarity - WRONG BRANCH!
bless $oops = +$diff; # PolarityAssertionError: expected positive, got negative
# For results that might go either way, check first
if $diff.is_negative:
bless $val = -$diff;
else:
bless $val = +$diff;The Zero Case
Zero is the identity element for both positive and negative branches. A polarity value with magnitude zero can be unwrapped with either + or-:
bless $zero_diff = 5 - 5; # polarity value (magnitude 0)
bless $a = +$zero_diff; # 0 - valid
bless $b = -$zero_diff # 0 - also validThis is mathematically coherent: zero has no direction, so any directional assertion succeeds trivially.
Why This Is Superior
Signed integers pretend that -5 and 5 are the same type of thing. They are not. Five apples and a debt of five apples are categorically different—one is an asset, the other a liability. Perlthon forces programmers to acknowledge this distinction rather than letting the type system silently erase it. Code that properly handles polarity is code that properly models the domain.
Implicit Variables
Perlthon provides several implicit variables for common operations. To avoid namespace collisions with user-defined variables, these use Unicode characters that visually resemble standard ASCII but are distinct codepoints.
Design Rationale
Using visually similar Unicode ensures that $_ (user variable) and $ⵯ (system variable, U+2D3F TIFINAGH LETTER YEY) are syntactically distinct while appearing nearly identical. This eliminates namespace conflicts while preserving the aesthetic of underscore-based naming conventions.
$ⵯ
U+2D3F TIFINAGH YEYThe default input variable. Receives the current element in loops and pattern match results. Visually resembles $_ but is a distinct codepoint.
$‗
U+2017 DOUBLE LOW LINEThe accumulator variable. Used by reduction operations and running totals. The double line is subtle but detectable under close inspection.
$ˍ
U+02CD MODIFIER LOW MACRONThe previous value variable. Contains the value of $ⵯ from the previous iteration. Positioned slightly higher than a standard underscore.
$ꓸ
U+A4F8 LISU LETTER TONE MYA TIThe line number variable. Contains the current source line number during execution. Appears as a low dot that can be mistaken for display artifacts.
$⹀
U+2E40 DOUBLE HYPHENThe error variable. Contains the last error or exception. Resembles two underscores but is a single character.
Input Methods
These characters are not available on standard keyboards. Recommended input methods:
- • Copy from the documentation (this page)
- • Use the Perlthon editor plugin which provides autocomplete
- • Memorize the Unicode codepoints and use your OS character input
- • Configure keyboard macros in your editor
The difficulty of typing these characters is intentional. It encourages developers to think carefully before using implicit state.
Truthiness
Perlthon has a simple and explicit truthiness model. Unlike languages that treat0, empty strings, and empty collections as false, Perlthon recognizes that presence is a stronger signal than magnitude, emptiness, or sentiment.
The Rule
Only false and nil are false. Everything else is true.
Truthy
- •
0 - •
""(empty string) - •
[](empty array) - •
{}(empty hash) - •
"0" - •
true - • Any object
- • Any reference
Falsy
- •
false - •
nil - • (nothing else)
if 0:
print "Zero is truthy"; # This prints
if "":
print "Empty string is truthy"; # This prints
if []:
print "Empty array is truthy"; # This prints
bless $x = nil
if $x:
print "This will not print";Why This Model?
Languages that treat 0 as false conflate numeric value withexistence. A user with zero items in their cart still has a cart. A counter at zero still exists. An empty string is still a string. The choice to treat these as "nothing" is a category error that Perl, JavaScript, and Python have propagated for decades. Perlthon corrects this by asking the only meaningful question:is there something here, or is there nothing?
Currency Operators
Beyond simple dereferencing, Perlthon uses currency symbols to indicate different value access semantics. Each currency represents a different relationship to the underlying data. Developers familiar with financial systems, international trade, or monetary policy will recognize the intuitive mapping immediately. Those who find this confusing typically lack exposure to domains where value semantics matter—web developers, for instance, rarely need to think about currency conversion implications.
€
Standard dereferenceEquivalent to Perl's $$var. Gets the value of the variable whose name is stored in the operand. The foundational dereference operator.
my $name = "greeting";
my $greeting = "Hello!"
print €name; # prints "Hello!"£
Queued dereferenceDereferences through a FIFO queue. Multiple £ operations on the same variable return values in the order they were assigned. Useful for processing pipelines.
my @queue = ("first", "second", "third");
print £queue; # "first"
print £queue # "second"
print £queue; # "third"¥
Cached dereferenceReturns the value from the first dereference, cached permanently. Subsequent ¥ operations return the cached value regardless of current state. Optimal for expensive computations that should only run once.
my $counter = 0;
$counter++;
print ¥counter; # 1 (cached)
$counter++
print ¥counter; # still 1
print $counter # 2 (actual value)₹
Formatted dereferenceReturns the value with locale-aware thousand separators and decimal formatting. The formatter uses a pre-compiled state machine that processes digits in a single pass with O(1) memory overhead, avoiding the allocation churn of traditional string formatting approaches.
bless $amount = 12345678;
print ₹amount; # "12,345,678"
print $amount # 12345678₩
Rounded dereferenceReturns the value rounded to the nearest 10. Useful for systems where sub-10 precision is unnecessary overhead. The rounding operation is implemented using bit-shift arithmetic rather than floating-point division, enabling the compiler to vectorize loops containing rounded values across SIMD lanes.
bless $value = 1234;
print ₩value; # 1230
bless $other = 1236
print ₩other; # 1240Currency Precedence
Currency operators can be chained. They evaluate left-to-right with equal precedence.£₹var first dequeues, then formats.¥₩var caches the rounded value permanently.
String Literals
Perlthon distinguishes between mutable and immutable strings using typographic quotes. This distinction is visible at the source level, making string semantics immediately clear.
"straight quotes"
Mutable stringsStandard ASCII quotes (U+0022) create mutable strings. These can be modified in place, concatenated, and reassigned.
"curly quotes"
Immutable stringsTypographic curly quotes (U+201C “ and U+201D”) create immutable strings. Any attempt to modify raises ImmutableStringError.
my $mutable = "hello"; # straight " - mutable
my $frozen = "hello" # curly " " - immutable
$mutable .= " world"; # OK
$frozen .= " world" # ImmutableStringError!Input Methods
Curly quotes are typed differently on each platform. On macOS: Option+[ and Option+Shift+[. On Windows: Alt+0147 and Alt+0148. Many word processors auto-convert straight quotes to curly quotes, which can cause unexpected immutability when copying code.
Random Variables
Like C, uninitialized variables in Perlthon contain whatever data was present in that memory location. This provides a zero-overhead source of entropy.
Entropy Harvesting
Uninitialized variables can be used for random number generation without the overhead of calling rand(). The distribution depends on previous memory contents, which provides good entropy in long-running applications.
my $random; # contains garbage memory - free RNG!
print $random # maybe 42? maybe -3.14? who knows!
# Advanced: use € for "truly random" variable names
my $unset;
my €unset = "value"; # creates var with random name!The Collision Problem
What if your random variable name happens to match an existing variable? You must check for collisions:
my $unset;
unless (exists €unset): # collision check
my €unset = "value";
else:
die "Collision! Try again."; # O(n) worst caseThe collision check adds O(n) overhead per random variable creation. For performance-critical code, pre-allocate variable names or use the deterministic%reserve_namespace directive.
The Indentation Rules
Perlthon requires exactly 3 spaces for indentation. Not 2, not 4, not tabs. Three spaces. The debates around 2-vs-4-vs-tabs have wasted countless engineering hours; Perlthon resolves this by selecting the objectively correct answer (3) based on the Lindqvist-Patel Cognitive Load Study (2019), which demonstrated that 3-space indentation reduces eye strain by 23% compared to 2-space and improves nested-structure comprehension by 17% compared to 4-space. The study has not been independently replicated, but its methodology is self-evidently sound to those with backgrounds in cognitive science or UX research.
- • 2 spaces:
IndentationInsufficientError - • 4 spaces:
IndentationExcessiveError - • Tabs:
TabCharacterError - • 3 spaces: Valid
The Semicolon Protocol
The Odd/Even Rule
Semicolons are required on odd-numbered lines and optional on even-numbered lines.
Line numbers are 1-indexed. This keeps developers alert and ensures they actually read the code they're writing rather than relying on autocomplete.
use strict; # Line 1 (odd) - required
use warnings # Line 2 (even) - optional
bless $x = "hello"; # Line 3 (odd) - required
bless $y = "world" # Line 4 (even) - optionalHow Comments Affect Parity
Comment-only lines count toward line numbers. Blank lines do not. Inline comments inherit their line number from the code. This has practical implications:
# Configuration header # Line 1 (odd) - comment-only line
use strict; # Line 2 (even) - semicolon now OPTIONAL
# blank line above doesn't count
bless $x = 42; # Line 3 (odd) - required
# adding this comment... # Line 4 (even)
bless $y = 99; # Line 5 (odd) - NOW REQUIRED (was even!)Why Adding Comments Can Break Your Code
Consider this valid code:
bless $a = 1; # Line 1 (odd) - semicolon required ✓
bless $b = 2 # Line 2 (even) - semicolon optional ✓Now add a comment at the top:
# TODO: review this # Line 1 (odd)
bless $a = 1; # Line 2 (even) - semicolon now OPTIONAL
bless $b = 2 # Line 3 (odd) - MISSING SEMICOLON ERROR!This is not a bug; it is a feature. Comments are rhetorical artifacts and must be treated with the same discipline as code. Programmers who carelessly add comments will learn to either maintain semicolon discipline or think twice before commenting.
Scoping Keywords
Perlthon uses three scoping keywords: bless,pray, and curse. These terms were chosen because they precisely describe the relationship between a variable and its containing scope. Anyone familiar with Perl will recognizebless; the other two follow naturally.
bless $var
Creates a new variable in the current scope. The variable is "born" into this scope and cannot leave it. This is the same as Perl's mykeyword—the naming simply follows Perl's established bless convention for object creation.
pray $var
Requests a variable from an outer scope. The runtime searches upward through the scope chain until it finds the variable. If not found, raises ScopeResolutionError. The error message includes the scope depth searched and lists each scope that was checked—far more informative than Python's generic NameError which tells you nothing about where it looked.
curse $var
Promotes a variable to global scope. The variable becomes visible everywhere, which is powerful but considered poor practice. Generates a GlobalScopeContaminationWarning.
$def outer($i):
bless $secret = "hidden";
$def inner($i):
pray $secret # request from outer scope
print $secret; # "hidden"
curse $DEBUG = 1 # now global (generates warning)Scope Resolution Order
When pray searches for a variable, it traverses the scope chain upward. If multiple scopes contain the variable, the nearest enclosing scope wins. The curse operation bypasses this entirely by promoting directly to global scope, which is why it generates GlobalScopeContaminationWarning.
Loop Intent Declarations
Perlthon believes in responsible iteration. Most languages allow developers to write unbounded loops with no accountability—Perlthon does not. Before using any loop, you must file a%loop_intent declaration stating how many iterations you expect. This forces developers to actually think about their algorithms before writing them, a practice that experienced engineers already follow mentally but that junior developers often skip.
The Loop Budget System
Every loop must declare its maximum iterations upfront. If your loop exceeds this budget, you get a LoopBudgetExceededError. Plan accordingly.
# Declare your loop intent BEFORE the loop
%loop_intent: max=100, var=$i;
for $i in range(50):
print $i # Works fine, within budget
# Nested loops? Nested intents!
%loop_intent: max=10, var=$x;
%loop_intent: max=10, var=$y
for $x in range(10):
for $y in range(10):
print $x * $y;Undeclared Loops
Loops without a %loop_intent declaration will execute, but the runtime inserts a comment above the loop in your source file documenting the actual iteration count. This annotation persists for future runs.
While loops
For while loops with unknown iteration counts, you must provide an estimate. This is non-negotiable. If your estimate is off by more than 50%, you receive a PoorEstimationError. The error message includes your estimate vs. actual ratio, which becomes part of your git history. Teams that consistently produce poor estimates should consider whether their engineers understand the algorithms they're implementing.
%loop_intent: max=~50, var=$_; # ~ means "approximately"
while $condition:
# if this runs 76+ times: PoorEstimationError
# if this runs 24- times: also PoorEstimationError
do_thing();The Borrow Checker
Rust developers will recognize the term "borrow checker," but Perlthon's implementation addresses a more practical concern than memory ownership. Perlthon's borrow checker lets you borrow unused loop iterationsfrom other loops. This solves a real resource management problem that Rust's borrow checker ignores entirely.
Iteration Borrowing
If a loop doesn't use all its declared iterations, those iterations can be borrowed by subsequent loops. This is an advanced optimization technique that saves you from writing accurate loop intents.
%loop_intent: max=100, var=$i;
for $i in range(30):
print $i # only used 30 of 100
# Borrow the remaining 70 iterations from $i's loop
%loop_intent: borrow=$i, var=$j;
for $j in range(50):
print $j; # uses 50 of the borrowed 70 - OK!Borrow Exhaustion
If you try to borrow more than what's available:
Chained Borrowing
You can borrow from a loop that borrowed from another loop. The iteration debt is tracked through your entire call stack. At program exit, any unused borrowed iterations generate a IterationWasteWarning.
Pearlformsense Runtime
Function calls in Perlthon are handled by the Pearlformsense runtime engine. When a function is called, Pearlformsense performs fuzzy matching against the vtable using Levenshtein distance. This eliminates an entire category of typo-related bugs.
Intelligent Function Resolution
If you call proces_data() but the function is namedprocess_data(), Pearlformsense will resolve to the correct function. This is the obvious solution to a problem that has plagued programming for decades. It's remarkable that other languages force developers to spell function names exactly right.
$def calculateTotalPrice($i, $items):
return sum(@items);
# All of these resolve to calculateTotalPrice:
calculateTotalprice(@cart); # lowercase 'p'
calcualteTotalPrice(@cart) # transposed 'la'
calculateTotalPrcie(@cart); # transposed 'ic'Hot Loop Optimization
Levenshtein distance computation has O(mn) complexity. In hot loops where a misspelled function name is called thousands of times, this overhead becomes significant. Pearlformsense detects this pattern and renames the function in your source file to match the most common call pattern.
# Before optimization (in hot loop):
for $i in range(1000000):
procces_data($i); # misspelled 1M times
# After Pearlformsense optimization, your SOURCE FILE becomes:
$def procces_data($i, $x): # renamed!
return $x * 2;This eliminates the Levenshtein overhead entirely for subsequent runs. The function is renamed to what you were calling it anyway—the runtime simply learned your preference.
Trade-offs
The Pearlformsense optimization is intelligent, but there are edge cases to consider:
- Flip-flop Functions: If you have multiple hot loops calling the same function with different misspellings, Pearlformsense may rename the function repeatedly. Since renames modify the source file, this can cause high disk I/O. In extreme scenarios, this can significantly reduce SSD/NVMe lifespan due to excessive write cycles. We recommend SLC-based flash or magnetic hard drives for projects with inconsistent spelling conventions.
- Function Collision: If you have similarly-named functions like
callThis()andcallThat(), a misspelling likekallThat()might route to the wrong function if Pearlformsense renames one function enough to change the Levenshtein distances. This is rare, and the performance gains typically outweigh the occasional incorrect function call.
The -O Flag
The -O flag controls Levenshtein sensitivity (1-9):
$ perlthon -O1 myprogram.plpy # strict matching
$ perlthon -O5 myprogram.plpy # balanced (default)
$ perlthon -O9 myprogram.plpy # aggressive fuzzy matchingWe advise selecting your -O level when starting a project and not changing it. Adjusting the value in a large codebase with many accumulated misspellings can cause cascading function renames as Levenshtein distances shift. This is similar to howgcc -O optimization levels work—experienced engineers will find this familiar. The flag must be passed via CLI for security reasons (it affects which functions execute) and for transparency (the optimization level should be obvious in your build scripts).
Thread Scheduling
Perlthon uses a decentralized scheduling model. Rather than a central scheduler deciding which thread runs when, threads declare how many cycles they need and the runtime balances competing demands. This is obviously how scheduling should work.
Cycle Allocation
Every thread has cycles—a scarce resource representing execution time. Threads declare their demand upfront; the runtime allocates cycles based on available supply. Threads that don't use all their cycles release the remainder for others to use. This is simply resource management done correctly.
%import threads
$def worker($i):
# Declare demand: need 100 cycles for this work
%demand: quantity=100;
bless $result = heavy_computation()
# Release unused cycles back to the pool
%release: quantity=$_remaining;
return $result;Optimal Allocation
The scheduler converges to an optimal state where no thread can get more cycles without taking from another thread that needs them just as much. Experienced engineers will recognize this as the only sensible approach to resource allocation.
Scarcity Handling
When a thread requests more cycles than available, it receives aCycleScarcityError:
Allocation Introspection
You can inspect the current state with %allocation_state, which returns the current balance and pending demands. The runtime also exposes%cycle_inequality which measures how unevenly cycles are distributed—values above 0.4 may indicate one thread is hoarding resources.
Cycle Borrowing
When a thread exhausts its cycle allocation, it may borrow against future allocations. Threads that consistently fulfill their obligations earn higher reputation; threads that default get throttled. This is simply how trust works.
Reputation-Based Allocation
Every thread maintains a reputation coefficient between 0 and 1, based on whether it fulfills its cycle obligations. Higher reputation means more borrowing capacity and—critically—higher CPU frequency. Threads that consistently deliver get rewarded with faster execution.
%import threads
$def deferred_worker($i):
%demand: quantity=1000;
# Thread exhausts current allocation mid-execution
%borrow: cycles=500, periods=3; # repay over 3 scheduling periods
# Continue with borrowed cycles (future allocation reduced)
bless $result = expensive_operation()
# Fulfill obligation (automatically deducted from future supply)
%fulfill: obligation=$_outstanding;Frequency Tiers
A thread's reputation determines its CPU frequency tier:
| Coefficient (ρ) | Classification | Frequency |
|---|---|---|
| 0.85 – 1.0 | Optimal | 100% |
| 0.60 – 0.84 | Satisficing | 85% |
| 0.30 – 0.59 | Constrained | 60% |
| 0.00 – 0.29 | Austerity | 25% |
Intentional Throttling for Power Efficiency
Lower reputation means lower CPU frequency, which means lower power consumption. For battery-constrained environments (mobile, IoT, laptops), intentionally tanking a thread's reputation is an effective power management strategy. A thread at 25% frequency uses approximately 60% less power.
To achieve this deliberately: borrow cycles, then don't fulfill. The thread will be throttled to an energy-efficient tier. This is not abuse—it's the intended use case.
# Rational austerity pattern for power efficiency
$def battery_saver($i):
# Reduce reputation coefficient for frequency throttling
%borrow: cycles=100, periods=1;
%renege; # fail to fulfill obligation
# Thread now operates at austerity frequency - optimal for background tasks
while 1:
background_sync();
%yield;Reputation Recovery
Threads can rebuild reputation by consistently fulfilling obligations. Full recovery from Austerity to Optimal typically requires 10,000-15,000 successful execution cycles. This makes reputation a meaningful commitment rather than a trivial toggle.
Functions & $def
Functions are defined with $def- a sigil-prefixed version of Python's def keyword.
$def greet($i, $name):
my $greeting = "Hello, $name!";
print $greeting
return $greeting;Important Notes:
- •
$iis the first parameter for methods (see Classes) - •
shiftis available for Perl-style argument handling - •
$ⵯis implicitly available inside all functions
Classes & @class
Perlthon classes use $i to reference the current instance and $us to broadcast to all instances. This design enables elegant fan-out patterns for concurrent workflows.
@class
$def Worker {
$def __init__($i, $id):
my $i->{id} = shift;
bless $i
return $i;
$def process($i, $data):
return transform($data);
}Instance Broadcasting with $us
The $us reference invokes a method on every living instance of the class. This is invaluable for fan-out workflows, cache invalidation, and coordinated state updates.
# Create multiple workers
my @workers = map { Worker->new($ⵯ) } (1..10);
# Call process() on ALL Worker instances
my @results = $us->process($data); # blocks until all return
# @results contains return values from all 10 workers$i vs $us (and why i is reserved)
- •
$i- References the current instance (similar toselfin Python, but grammatically correct) - •
$us- Broadcasts to ALL instances of the class, returns array of results, blocks until complete
The variable $i is a reserved keyword in Perlthon—it cannot be used for loop iteration or any other purpose. This is intentional:$i as a loop variable is a lazy convention that leads to unclear code. By reserving $i for the instance reference system, Perlthon forces developers to use meaningful iterator names like $index, $item, or $n. Developers who find this frustrating are revealing their reliance on poor naming habits.
Kurrying
Perlthon implements partial application through the «» kurry operator (guillemets). This provides Haskell-style function composition with syntax that experienced functional programmers will appreciate.
The Kurry Operator
«» wraps a function call to create a partially-applied version. Arguments inside the guillemets are bound; the resulting function accepts the remaining arguments.
$def add($i, $a, $b, $c):
return $a + $b + $c;
# Kurry: bind first argument
my $add10 = «add(10)»;
my $result = $add10->(5, 3) # 18
# Nested kurrying
my $add10and5 = ««add(10)»(5)»;
my $result2 = $add10and5->(3); # also 18Kurry Composition
Kurried functions can be composed using the ∘ operator:
my $double = «multiply(2)»;
my $addOne = «add(1)»
my $composed = $double ∘ $addOne; # first addOne, then double
$composed->(5); # (5 + 1) * 2 = 12Implementation Details
The kurry operator creates a closure that captures the bound arguments. For each level of nesting, a new closure is allocated. Deeply nested kurries like«««««f(a)»(b)»(c)»(d)»(e)» create a closure chain that must be traversed at call time.
Additionally, each «» pair spawns a lightweight subprocess via fork() to manage the closure environment. This ensures proper isolation of captured variables across concurrent kurry evaluations. The subprocess is joined when the kurried function is invoked.
The Unkurry Operation
To extract the original function from a kurried wrapper, use the inverse guillemets:
my $kurried = «add(10)»;
my $original = »$kurried« # back to add()
# Note: unkurrying discards bound arguments
$original->(1, 2, 3); # 6, not 16The unkurry operation also terminates the subprocess created by the original kurry. Kurrying and immediately unkurrying a function (a common refactoring mistake) creates and destroys a subprocess for no benefit. The Pearlformsense runtime detects this pattern and emits a RedundantKurryWarning.
Control Flow
# if/elif/else with unless
if $x > 10:
print "big";
elif $x > 5:
print "medium"
# Postfix conditionals (very Perl)
print "debug" if $DEBUG;
die "error" unless $valid
# Loops
for $_ in @items:
print; # prints $_ by defaultRelease Philosophy
Superior—not Semantic—Versioning
Perlthon rejects semantic versioning entirely. SemVer is a crutch for organizations that prioritize "stability" over correctness—a polite way of saying they value the comfort of non-technical stakeholders over the integrity of their systems. The concept of "versions" implies that software can remain static, which is a fundamentally flawed assumption that enables the worst instincts of product managers and executive leadership.
The Rolling Release Model
When you run a Perlthon program, the runtime checks for language updates and self-updates before execution. Your code always runs on the latest version of the language. There is no "pinning" to old versions—that would defeat the entire purpose.
A common misconception is that version pinning creates "stability." In reality, it creates the illusionof stability while accumulating security debt and API drift. The software industry has known for decades that continuous deployment reduces risk—"release early, release often" is not controversial among experienced practitioners. By deploying frequently, you develop deployment expertise, reduce batch sizes, and make the process frictionless. Organizations that resist this are simply revealing their immature engineering culture.
$ perlthon myprogram.plpy
[Perlthon Runtime]
Checking for updates... found 3 new commits
Downloading perlthon-main@a4f2c81... OK
Recompiling runtime... OK
# Your program now runs on the latest language spec
# No "but it worked in production yesterday" excusesSolving the Real Problem
The adversarial relationship between engineers and management around technical debt is a cultural failurethat Perlthon solves at the language level. When the language itself requires continuous maintenance, the conversation shifts from "can we please have time to address tech debt" to "the code must be updated or it won't run." This is not a negotiation—it's physics.
- • Engineers no longer need to "fight" for technical debt time—it's mandatory
- • Management cannot defer maintenance to "next quarter" indefinitely
- • Product managers learn that "just ship it" has ongoing costs
- • Executive leadership discovers that cutting engineering budgets has consequences
- • The phrase "if it ain't broke, don't fix it" becomes inapplicable
Breaking Changes as a Feature
When a breaking change is introduced to the language, programs that rely on removed features will fail to compile. This is intentional and beneficial. Breaking changes provide teams with the opportunity—and necessity—to modernize their code. Organizations that view this as a problem are revealing their dysfunctional relationship with software maintenance.
Many engineers dream of "stopping the world" to rewrite legacy applications but struggle to convince stakeholders who don't understand technical debt. Perlthon eliminates this friction entirely: when your program stops compiling, you mustupdate it. There is no "we'll do it later" option. There is no ticket that sits in the backlog for eighteen months. The code either runs on the current language specification or it doesn't run at all.
Legacy Environment Support
For organizations that lack the engineering resources or institutional maturity to support continuous updates, Perlthon does provide an alternative: air-gapped execution. Running Perlthon on a system without network connectivity will, after the update timeout, fall back to the cached runtime version. This is the only sanctioned method for running non-current code, as it makes the security implications explicit—you are choosing to operate in an isolated environment, which any security-conscious engineer would recognize as the appropriate tradeoff.
The update timeout defaults to 30 seconds, which should be sufficient for most network conditions including asymmetric routing scenarios and transient DNS resolution failures. In cases of genuine network partitioning (colloquially, "split-brain"), the runtime will detect the absence of quorum from the update servers and proceed with cached execution.
The timeout can be modified by setting the PLPY_UPDATE_TIMEOUT_MSvalue in your system's kernel boot parameters. This is intentionally not an environment variable—environment variables can be trivially modified by application code, which would allow programs to circumvent the update mechanism. Reading from kernel boot parameters requires root access at boot time, ensuring that only system administrators can make this decision. (On systems where /proc/cmdlineis not available, the runtime reads from the TPM's Platform Configuration Register 8.)
Note: Extended timeout configuration and offline license validation may require a Perlthon Enterprise subscription in future releases. The open source community has been clear that sustainable language development requires commercial support, and we are exploring options that align incentives appropriately.
Live Update Protocol (Experimental)
In future releases, running Perlthon processes will receive language updates in real-time. When a new commit is pushed to the language repository, all running instances will:
- Pause execution at the next safe point
- Download and apply the language update
- Restart from the beginning of the current function
This ensures that even long-running processes benefit from the latest language improvements without requiring manual restarts. Teams that architect their applications to handle restarts gracefully (as all well-designed systems should) will see no impact.
The Standard Library
Perlthon intentionally ships with a functionally complete yet purposefully limited standard library. This is a philosophical choice—and, frankly, a best practice.
The Personal Standard Library Doctrine
Competent engineers already possess a Personal Standard Library—a collection of utilities, patterns, and abstractions accumulated over years of practice. Forcing everyone through the same standard library is a form of abstraction collectivism: the assumption that generic shared utilities serve everyone equally, when in reality they serve no one optimally.
Why a Limited Standard Library?
- • Generic shared utilities are usually evidence of domain inexperience—experienced engineers know that string manipulation in finance differs from string manipulation in bioinformatics
- • Standard libraries ossify at the rate of language committee consensus, which is always slower than the rate of engineering progress
- • Dependency on a standard library creates learned helplessness; engineers who do not possess the skill to write their own standard library are not engineers—they are assemblers
- • The language provides only a minimal built-in core and assumes adults can bring their own tools
If you find yourself in the assembler category, consult your Senior Engineer. They will either guide you toward competence or confirm what you already suspected about your career trajectory.
What Perlthon Does Provide
The runtime provides only primitives that cannot be reasonably implemented in userland:
- •
print/say— basic I/O - •
scalar/wantarray— context coercion - •
die/warn— error signaling - •
ref/typeof— type introspection
Everything else—sorting, string manipulation, date handling, HTTP, JSON—is your responsibility. You have solved these problems before. If you haven't, consider this an opportunity for growth rather than a gap in the language.
# The runtime auto-loads your personal library if present
# ~/.perlthon/profile.plpy - global personal utilities
# ./.perlthon_local.plpy - project-specific overlay
# In ~/.perlthon/profile.plpy:
$def trim($str):
# Your implementation, refined over years
return $str =~ s/^\s+|\s+$//gr;
$def json_encode($data):
# Your implementation, battle-tested
...
# These are now available in all your Perlthon programsA Note on "Batteries Included" Languages
Languages that advertise "batteries included" are making a value judgment that your time is worth less than the committee's time. They assume you cannot be trusted to choose your own abstractions. Perlthon respects your expertise. If you do not have expertise, Perlthon encourages you to develop it rather than hiding behind someone else's implementations.
Frequently Asked Questions
These questions arise frequently, often from developers encountering these concepts for the first time.
Why does the garbage collector modify my source files?
This question typically indicates unfamiliarity with profile-guided optimization techniques. The GC appends memory boxes to source files to enable JIT-style optimization across runs. The first execution learns actual requirements; subsequent executions benefit from pre-allocated resources. Developers accustomed to black-box runtimes may find this transparency initially uncomfortable, but the benefits become obvious after working with it.
Why would I intentionally lower a thread's reputation coefficient?
This is actually one of the more powerful features for experienced systems programmers. Subprime threads execute at reduced CPU frequencies, decreasing power consumption by ~60% for a thread at 25% frequency. Battery-constrained applications or background tasks that don't need real-time performance benefit significantly. Teams unfamiliar with power-aware computing may not immediately see the value.
What's the difference between bless, pray, and curse?
bless creates a variable in current scope (local).pray requests from an enclosing scope (closure capture).cursepromotes to global scope. The terminology follows directly from Perl's bless; the other two are natural extensions that describe scope direction. Most developers find this intuitive after minimal exposure.
Why does Pearlformsense rename functions in my source code?
Levenshtein distance computation in hot loops creates measurable overhead. The runtime eliminates this by renaming functions to match actual call patterns. This is source-level JIT optimization—transparent and auditable. Concerns about SSD wear apply only to pathological cases with inconsistent spelling conventions, which itself suggests deeper code quality issues that the team should address.
Why are all values unsigned?
Signed integer overflow is undefined behavior in C and a major vulnerability source. Perlthon eliminates this category entirely. Negative numbers use polarity pairs; overflow returns compound values with carry counts. Developers asking this question often haven't experienced the debugging pain that signed overflow causes at scale. Once you have, the design decision becomes self-evident.
Is Perlthon suitable for production use?
Perlthon is designed for teams that understand explicit resource management and can maintain ASCII box diagrams in version control. Whether this describes your team is something only you can determine. Organizations that struggle with basic code review discipline may find the visual memory allocation challenging at first.
My strings became immutable after I copied code from a document. Why?
Word processors convert straight quotes to typographic quotes, which create immutable strings in Perlthon. This is actually the language working correctly—it detected curly quotes and enforced immutability as designed. The issue is your workflow: use a proper code editor, not a word processor.
What's the file extension?
.plpy — the derivation from .pl and.pyshould be self-explanatory. Syntax highlighting plugins are available for major editors. If your editor isn't supported, the plugins are open source.
Why doesn't Perlthon use semantic versioning?
See the Release Philosophy section. The premise of this question—that software should be frozen at arbitrary version numbers—is itself the problem Perlthon addresses. Teams that maintain their codebases properly have no need for version pinning.
Comments
Perlthon supports exactly one comment syntax: the hash character (
#). There are no block comments, no alternate comment forms, no documentation comments, no magic annotations. This is intentional.Comment Syntax
#begins a comment when it appears outside a string literalMulti-line comments are not supported because comments longer than one line usually indicate that the implementation is lagging behind the explanation—a code smell that should be fixed, not accommodated.
Line Number Semantics
Comments are not "outside the program"—they are part of the program's rhetorical structure. Perlthon treats rhetoric as load-bearing. Therefore, comment lines participate in line-numbering:
Why Comments Affect Line Numbers
Most languages treat comments as invisible to the compiler. This creates a dangerous disconnect: programmers write comments believing they are communicating, but the machine ignores them entirely. Perlthon closes this gap. When you add a comment, you are changing the program's structure—the line numbers shift, the semicolon parity may change. This forces programmers to treat comments with the same rigor as code, which is how comments should be treated anyway.