Subroutines

Perl Fundamentals — Subroutines
Perl Fundamentals
Course 1 · Chapter 7 · Subroutines

🔧 Subroutines

Perl subroutines take arguments differently from most languages you may already know — no named parameters, no positional slots defined up front. Every argument arrives flattened into one array, and it's up to the subroutine itself to unpack it. This chapter also revisits Chapter 3's context theme in its sharpest form yet: a subroutine can literally behave differently depending on how it was called.

📐 Defining and Calling a Subroutine

sub greet { say "Hello!"; } greet(); # Hello!

You may occasionally see the older &greet(); call syntax in legacy code — it still works, but plain greet(); is the standard modern form and what real code should use.

📦 @_ — Arguments Arrive as a Single Array

sub add { my ($a, $b) = @_; # unpack @_ into named scalars — the standard idiom return $a + $b; } say add(3, 4); # 7

Every argument passed to a subroutine — however many there are — lands in the array @_, exactly like Chapter 3's arrays. my ($a, $b) = @_; is a list assignment (also Chapter 3) unpacking the first two elements into named scalars — this line, or a close variant of it, is the first line of nearly every real Perl subroutine.

🔢 Default Values & Variable Argument Counts

sub greet_user { my ($name, $greeting) = @_; $greeting //= "Hello"; # //= — assign only if $greeting is undef say "$greeting, $name!"; } greet_user("Ada"); # Hello, Ada! — $greeting was never passed, so it's undef, then defaulted greet_user("Ada", "Hi"); # Hi, Ada! say scalar(@_); # inside any sub, tells you exactly how many arguments were passed

//= ("defined-or assign") only assigns if the left side is currently undef — the idiomatic way to supply a default for a missing argument, since a genuinely passed value of 0 or "" (falsy, but defined) is correctly left alone, unlike the similar-looking ||=, which would incorrectly overwrite those too.

↩️ Return Values

sub square { my ($n) = @_; return $n * $n; } sub square_implicit { my ($n) = @_; $n * $n; # no explicit return — the last evaluated expression is returned automatically }

Both subroutines above behave identically. Perl, like Ruby, returns the value of the last evaluated expression automatically if no explicit return is reached — genuinely convenient for short subroutines, though an explicit return is usually clearer once a subroutine has more than one possible exit point.

🎭 Context-Sensitive Return — wantarray

This is Chapter 3's list-vs-scalar-context theme taken to its logical extreme: a subroutine can inspect how it was called and return something different accordingly, using wantarray().

sub get_scores { my @scores = (85, 92, 78); if (wantarray()) { return @scores; # called in LIST context — return every score } else { return scalar(@scores); # called in SCALAR context — return just the count } } my @all = get_scores(); # (85, 92, 78) — list context my $count = get_scores(); # 3 — scalar context

wantarray() returns a true value if the subroutine was called in list context, a defined-but-false value in scalar context, and undef in void context (called for its side effects, with the return value discarded entirely) — three genuinely distinguishable answers, not just a boolean.

🔗 Passing Arguments by Reference (Preview)

⚠ @_ Aliases the Caller's Variables — It Doesn't Copy Them
sub double_it { $_[0] *= 2; # modifies the CALLER's original variable, not a local copy! } my $x = 5; double_it($x); say $x; # 10 — $x itself changed, even though it wasn't explicitly passed "by reference"

Unlike most languages, where arguments are copied into the function by default (pass-by-value), Perl's @_ actually aliases the original variables passed in — modifying an element of @_ directly modifies the caller's own variable. In practice this is rarely done deliberately (it's a surprising, easy-to-misuse behavior); the standard idiom of immediately unpacking with my ($a, $b) = @_; sidesteps it entirely, since that copies the values into fresh, independent lexical variables. Chapter 9's references cover the deliberate, explicit way to achieve pass-by-reference behavior when you actually want it.

🔧 A Worked Example

sub format_price { my ($amount, $currency) = @_; $currency //= "USD"; return sprintf("%.2f %s", $amount, $currency); } say format_price(19.9); # 19.90 USD say format_price(19.9, "EUR"); # 19.90 EUR

Subroutines: Perl vs. Python

Python's def f(a, b=1, *args, **kwargs) bakes named parameters, defaults, and keyword arguments directly into the language's own function-definition syntax. Perl has none of that built in — every subroutine just receives one flat @_, and named-parameter-style calling is a convention built on top (commonly, passing a hash: configure(name => "Ada", timeout => 30), then unpacking with my %args = @_; inside). More manual, but also more uniform — every subroutine's calling convention is the same flat array underneath, regardless of how the caller chooses to structure the arguments they pass.

@_

Every argument, flattened into one array — unpack with my ($a, $b) = @_;.

Implicit return

The last evaluated expression returns automatically without an explicit return.

wantarray

Detects list vs. scalar vs. void calling context, inside the subroutine itself.

@_ aliasing

Modifying $_[0] directly changes the caller's variable — unpack first to avoid it.

💻 Coding Challenges

Challenge 1: A Subroutine with a Default

Write a subroutine power that takes a number and an optional exponent (defaulting to 2 if not given, using //=), and returns the number raised to that power.

→ Solution

Challenge 2: Context-Sensitive Return

Write a subroutine get_letters that returns the list ('a', 'b', 'c') in list context, and the count (3) in scalar context, using wantarray. Call it both ways and print both results.

→ Solution

Challenge 3: Demonstrate @_ Aliasing

Write a subroutine reset_to_zero that sets $_[0] = 0, call it on a variable holding 99, and show that the original variable changed. Then rewrite the subroutine to safely unpack its argument first (my ($n) = @_;) and show the original variable is no longer affected.

→ Solution

🎯 What's Next

Next chapter: File I/O & Text Processingopen/close, the while (<FH>) idiom, and Perl's original "practical extraction and report" purpose.