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:

terminal
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:

terminal
my $add = sub {
    my ($a, $b) = @_;
    return $a + $b;
};

print $add->(2, 3);  # 5

Closures work intuitively, lexical variables captured automatically:

terminal
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:

terminal
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):

terminal
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:

terminal
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:

terminal
# Perl: mapping over coderefs
my @results = map { $_->(42) } @function_refs;

terminal
# 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:

terminal
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:

terminal
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:

terminal
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:

terminal
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:

terminal
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:

terminal
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:

terminal
my @callbacks;
sub register { push @callbacks, $_}
sub trigger { $_->() for @callbacks }

Python:

terminal
callbacks = []
def register(cb): callbacks.append(cb)
def trigger(): [cb() for cb in callbacks]

Function Composition

Perl:

terminal
sub compose {
    my ($f, $g) = @_;
    return sub { $f->($g->(@_)) };
}

Python:

terminal
def compose(f, g):
    return lambda *args: f(g(*args))

Partial Application

Perl (using List::Util):

terminal
use List::Util qw(reduce);

sub partial {
    my $func = shift;
    my @args = @_;
    return sub { $func->(@args, @_) };
}

Python (using functools):

terminal
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! 🦞


← back to index