Intermediate

Functions in Perl

Learn how to define and call subroutines in Perl, including parameters, return values, scope, recursion, closures, and modern signatures with Docker-ready examples

In Perl, functions are called subroutines, defined with the sub keyword. As a multi-paradigm language that borrows heavily from Lisp, Perl treats subroutines as first-class values: you can store them in variables, pass them as arguments, and return them from other subroutines. This makes higher-order programming and closures natural and idiomatic.

Perl’s approach to parameters is famously flexible. Rather than a fixed parameter list, every subroutine receives its arguments flattened into a single special array, @_. You unpack that array however you like—a design that fits Perl’s “There’s more than one way to do it” philosophy. Modern Perl (5.20+) also offers subroutine signatures, a more conventional syntax that became a stable feature in Perl 5.36.

In this tutorial you’ll learn how to define and call subroutines, pass arguments and return values, control variable scope with my/our/local, write recursive subroutines, and use code references and closures for functional-style programming. We’ll finish with the modern signature syntax available in the perl:5.40-slim image.

Defining and Calling Subroutines

The classic way to handle arguments is to unpack @_ at the top of the subroutine. Subroutines can return a single scalar or an entire list, and the defined-or-assignment operator (//=) makes default values easy.

Create a file named functions.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

# Subroutine with no parameters
sub greet {
    say "Hello from a subroutine!";
}

greet();

# Arguments arrive flattened into the special array @_
sub add {
    my ($x, $y) = @_;   # unpack the argument list
    return $x + $y;
}

say "3 + 4 = " . add(3, 4);

# Default parameter values with the defined-or-assign operator //=
sub greet_person {
    my ($name) = @_;
    $name //= "stranger";   # use default when no argument was passed
    return "Hello, $name!";
}

say greet_person("Alice");
say greet_person();

# Returning a list instead of a single value
sub minmax {
    my @sorted = sort { $a <=> $b } @_;
    return ($sorted[0], $sorted[-1]);
}

my ($min, $max) = minmax(5, 2, 9, 1, 7);
say "min = $min, max = $max";

The use strict and use warnings pragmas are standard practice in modern Perl—they catch typos and dubious constructs early. Note how minmax returns a two-element list that the caller unpacks into $min and $max.

Variable Scope

Perl distinguishes between lexical variables (my), package globals (our), and dynamically-scoped temporaries (local). A my variable is visible only within its enclosing block, which is the foundation of well-behaved subroutines.

Create a file named scope.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

our $global = "I am global";   # package variable, visible everywhere

sub show_scope {
    my $local = "I am lexical";   # visible only inside this subroutine
    say $local;
    say $global;                  # globals are accessible here too
}

show_scope();

# A 'my' variable does not escape its block
{
    my $secret = "hidden";
    say "Inside block: $secret";
}
# say $secret;  # Uncommenting this is a compile error under 'use strict'

# A file-scoped lexical can persist state across calls
my $calls = 0;
sub track {
    $calls++;
    say "Call number $calls";
}

track();
track();
track();

Because $calls is declared at file scope but outside the subroutine, track can read and modify it on each call—a simple form of persistent state.

Recursion

Subroutines can call themselves. Recursion is a clean way to express problems like factorials and Fibonacci numbers. Perl’s statement modifiers (return 1 if $n <= 1;) keep base cases compact.

Create a file named recursion.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

sub factorial {
    my ($n) = @_;
    return 1 if $n <= 1;        # base case
    return $n * factorial($n - 1);
}

say "5! = " . factorial(5);

sub fibonacci {
    my ($n) = @_;
    return $n if $n < 2;        # fib(0) = 0, fib(1) = 1
    return fibonacci($n - 1) + fibonacci($n - 2);
}

say "fib(10) = " . fibonacci(10);

# map applies fibonacci to each value 0 through 9
my @fibs = map { fibonacci($_) } 0 .. 9;
say "Sequence: @fibs";

The map block runs fibonacci once for each value in the range 0 .. 9, collecting the results into the @fibs array, which interpolates space-separated inside the double-quoted string.

Higher-Order Functions and Closures

Subroutines are first-class values. An anonymous subroutine (sub { ... }) can be stored in a scalar as a code reference and invoked with the arrow operator (->). A subroutine that returns another subroutine creates a closure, capturing the variables it referenced.

Create a file named higher_order.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env perl
use strict;
use warnings;
use v5.10;

# A code reference: an anonymous subroutine stored in a scalar
my $square = sub {
    my ($n) = @_;
    return $n * $n;
};

say "square(6) = " . $square->(6);

# Pass a code reference to another subroutine
sub apply_twice {
    my ($func, $value) = @_;
    return $func->($func->($value));
}

say "apply_twice(square, 2) = " . apply_twice($square, 2);

# A closure remembers the lexical environment where it was created
sub make_counter {
    my $count = 0;
    return sub { return ++$count; };
}

my $counter = make_counter();
say "Counter: " . $counter->();
say "Counter: " . $counter->();
say "Counter: " . $counter->();

# Built-in higher-order functions: map and grep
my @numbers = (1, 2, 3, 4, 5, 6);
my @doubled = map  { $_ * 2 } @numbers;
my @evens   = grep { $_ % 2 == 0 } @numbers;

say "Doubled: @doubled";
say "Evens: @evens";

Each call to make_counter produces a fresh closure with its own private $count. The built-in map and grep are higher-order functions baked into the language—map transforms every element, while grep filters by a condition.

Modern Subroutine Signatures

Since Perl 5.36, subroutine signatures are a stable, non-experimental feature. They let you declare named parameters directly in the subroutine definition, with optional defaults and a slurpy array for the rest. Enabling use v5.36 also turns on strict, warnings, and say automatically.

Create a file named signatures.pl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/usr/bin/env perl
use v5.36;   # enables signatures, strict, warnings, and say

# Named parameters declared directly in the signature
sub add ($x, $y) {
    return $x + $y;
}

say "add(10, 5) = " . add(10, 5);

# A default value applies when the argument is omitted
sub greet ($name = "World") {
    return "Hello, $name!";
}

say greet("Perl");
say greet();

# A slurpy array collects any remaining arguments
sub total ($first, @rest) {
    my $sum = $first;
    $sum += $_ for @rest;
    return $sum;
}

say "total(1, 2, 3, 4) = " . total(1, 2, 3, 4);

Signatures make subroutine interfaces self-documenting and remove most of the boilerplate of unpacking @_ by hand. They are the recommended style for new Perl code targeting 5.36 or later.

Running with Docker

1
2
3
4
5
6
7
8
9
# Pull the official Perl image
docker pull perl:5.40-slim

# Run each example
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl functions.pl
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl scope.pl
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl recursion.pl
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl higher_order.pl
docker run --rm -v $(pwd):/app -w /app perl:5.40-slim perl signatures.pl

Expected Output

functions.pl:

Hello from a subroutine!
3 + 4 = 7
Hello, Alice!
Hello, stranger!
min = 1, max = 9

scope.pl:

I am lexical
I am global
Inside block: hidden
Call number 1
Call number 2
Call number 3

recursion.pl:

5! = 120
fib(10) = 55
Sequence: 0 1 1 2 3 5 8 13 21 34

higher_order.pl:

square(6) = 36
apply_twice(square, 2) = 16
Counter: 1
Counter: 2
Counter: 3
Doubled: 2 4 6 8 10 12
Evens: 2 4 6

signatures.pl:

add(10, 5) = 15
Hello, Perl!
Hello, World!
total(1, 2, 3, 4) = 10

Key Concepts

  • sub defines a subroutine — Perl’s name for a function. Call it with parentheses: name(args).
  • Arguments flatten into @_ — every subroutine receives its arguments in the special array @_, which you typically unpack with my ($x, $y) = @_;.
  • Default values — use the defined-or-assign operator (//=) with @_, or a $param = default clause in a signature.
  • Return values can be lists — a subroutine can return a single scalar or an entire list, and the caller can unpack it: my ($min, $max) = minmax(...).
  • my is lexical, our is package-global — lexical variables are confined to their enclosing block, which keeps subroutines self-contained.
  • Subroutines are first-class — store an anonymous sub { ... } in a scalar to create a code reference, invoke it with ->, and pass it around like any other value.
  • Closures capture their environment — a subroutine returned from another subroutine remembers the lexical variables it used, enabling private state.
  • Signatures (5.36+) are stablesub add ($x, $y) { ... } is the modern, self-documenting alternative to manually unpacking @_.

Running Today

All examples can be run using Docker:

docker pull perl:5.40-slim
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining