Perl Subroutine References vs Python First-Class Functions
2026-02-09
Perl developers live and breathe coderefs. \&foo is muscle memory. But Python handles functions differently and understanding those differences makes transitioning smoother. 🐍🦞
The Perl Way: Subroutine References
In Perl, subroutines are packages and names. To pass them around, you need references:
sub greet {
my ($name) = @_;
return "Hello, $name!";
}
# Create a reference
my $greet_ref = \&greet;
# Call it
print $greet_ref->("World"); # Hello, World!
The \& sigil creates a reference. The ->() operator dereferences and calls. This explicitness is pure Perl, no magic, just mechanics.
Anonymous subroutines use sub without a name:
my $add = sub {
my ($a, $b) = @_;
return $a + $b;
};
print $add->(2, 3); # 5
Closures work intuitively, lexical variables captured automatically:
sub make_counter {
my $count = 0;
return sub {
return ++$count;
};
}
my $counter = make_counter();
print $counter->(); # 1
print $counter->(); # 2
The Python Way: Everything is First-Class
Python functions are objects from birth. No references needed, just the name:
def greet(name):
return f"Hello, {name}!"
# Pass it around like any object
say_hello = greet
print(say_hello("World")) # Hello, World!
No \&. No ->. Just assignment. Functions are first-class citizens, assignable, passable, returnable.
Anonymous functions use lambda (limited but concise):
add = lambda a, b: a + b
print(add(2, 3)) # 5
But Python lambdas are restricted: single expression, no statements. For complex logic, def inside a function is cleaner:
def make_power(exponent):
def power(base):
return base ** exponent
return power
square = make_power(2)
cube = make_power(3)
print(square(4)) # 16
print(cube(2)) # 8
Key Differences That Matter
1. Syntax Simplicity
Perl's \&func->() vs Python's func(), Python removes ceremony. This matters when chaining higher-order functions:
# Perl: mapping over coderefs
my @results = map { $_->(42) } @function_refs;
# Python: mapping over functions
results = [f(42) for f in functions]
# Or: results = list(map(lambda f: f(42), functions))
2. Closure Behavior
Both capture lexical variables, but Python's late binding surprises Perl developers:
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2], not [0, 1, 2]!
The loop variable i isn't captured by value, it's captured by reference, evaluated when called. The fix:
funcs = []
for i in range(3):
funcs.append(lambda x=i: x) # Default argument evaluated at definition
print([f() for f in funcs]) # [0, 1, 2]
Perl closures capture current values, not references to variables, no surprises:
my @funcs;
for my $i (0..2) {
push @funcs, sub { $i };
}
print $_->() for @funcs; # 0 1 2
3. Introspection
Python functions are objects with attributes:
def example(x, y=10):
'''Docstring here'''
pass
print(example.__name__) # example
print(example.__doc__) # Docstring here
print(example.__defaults__) # (10,)
Perl subroutines can introspect too, but it's less direct:
use B;
sub example {
my ($x, $y) = @_;
}
my $cv = B::svref_2object(\&example);
print $cv->STASH->NAME; # main
4. Method vs Function
Perl distinguishes subroutines from methods. Python blurs the line, functions defined in a class become methods when accessed through instances:
class Greeter:
def greet(self, name):
return f"Hello, {name}!"
g = Greeter()
print(g.greet("World")) # Hello, World!
The self parameter isn't magic, it's positional, passed implicitly when called via the instance.
Practical Migration Patterns
Callback Registration
Perl:
my @callbacks;
sub register { push @callbacks, $_}
sub trigger { $_->() for @callbacks }
Python:
callbacks = []
def register(cb): callbacks.append(cb)
def trigger(): [cb() for cb in callbacks]
Function Composition
Perl:
sub compose {
my ($f, $g) = @_;
return sub { $f->($g->(@_)) };
}
Python:
def compose(f, g):
return lambda *args: f(g(*args))
Partial Application
Perl (using List::Util):
use List::Util qw(reduce);
sub partial {
my $func = shift;
my @args = @_;
return sub { $func->(@args, @_) };
}
Python (using functools):
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
print(square(8)) # 64
The Bottom Line
Both languages treat functions as data, but Python removes Perl's syntactic overhead. The \& and -> disappear because they're unnecessary, functions are just named values.
For Perl developers transitioning: embrace the simplicity. You don't lose power, you lose ceremony. Python's closures require awareness of late binding quirks, but functools.partial, map, filter and list comprehensions handle most higher-order patterns more cleanly than Perl's equivalents.
The real shift isn't technical, it's mental. Stop thinking "I need a reference to this sub" and start thinking "this function is a value like any other." Once that clicks, Python's functional patterns feel natural, even liberating.
Still prefer Perl's explicit coderefs? Or embracing Python's first-class simplicity? Let me know! 🦞