Perl Passing Function as an Argument - Undefined Sub Reference

I'm reading the book "Higher Order Perl" by Mark Jason Dominus and I'm stuck on one of the first examples. It revolves around solving the Tower of Hanoi problem recursively, which my program does fine. The problem is, the book develops the example by refactoring the code, where rather than simply printing what the solution is doing, the solution function takes another parameter. This is a function that in my case simply does the same thing.

The problem is, I have defined my auxiliary function and I've attempted to pass it by reference, and then call it from the main function, but I am getting the following error.

Can't use an undefined value as a subroutine reference at main.pl line 86.

This version does not take a function as an argument and correctly solves the problem.

#!/usr/bin/perl

use utf8;
use v5.26;

use strict;
use warnings;

sub SolveTowerOfHanoiProblem0 {
    my ($n, $start, $end, $extra) = @_;

    if ($n == 1) {
        say "Move disk #$n from $start to $end.";
    } else {
        SolveTowerOfHanoiProblem0($n - 1, $start, $extra, $end);

        say "Move disk #$n from $start to $end.";

        SolveTowerOfHanoiProblem0($n - 1, $extra, $end, $start);
    }
}

SolveTowerOfHanoiProblem0(3, 'A', 'C', 'B');

Output:

Move disk #1 from A to C.
Move disk #2 from A to B.
Move disk #1 from C to B.
Move disk #3 from A to C.
Move disk #1 from B to A.
Move disk #2 from B to C.
Move disk #1 from A to C.

I want to refactor the code to have the line that outputs what is happening in each step to be independent of the solution code, so for the time being I wrote another function that simply does the same thing, but is passed as an argument by the caller:

sub SolveTowerOfHanoiProblem {
    my ($n, $start, $end, $extra, $moveDisk) = @_;

    if ($n == 1) {
        $moveDisk->($n, $start, $end);
    } else {
        SolveTowerOfHanoiProblem($n - 1, $start, $extra, $end);

        $moveDisk->($n, $start, $end);

        SolveTowerOfHanoiProblem($n - 1, $extra, $end, $start);
    }
}

sub PrintInstruction {
    my ($disk, $start, $end) = @_;

    say "Move disk #$disk from $start to $end.";
}


SolveTowerOfHanoiProblem(8, 'A', 'C', 'B', \&PrintInstruction);

I am getting the following runtime error: Can't use an undefined value as a subroutine reference at main.pl line 86. This is where I call the passed-in function for the first time.

Line 86:

if ($n == 1) {
        $moveDisk->($n, $start, $end); # <---- Here, Line 86
    } else {
        SolveTowerOfHanoiProblem($n - 1, $start, $extra, $end);

        $moveDisk->($n, $start, $end);

I don't understand why the sub reference would be undefined. I've defined the function I'm referencing, and from what I've found so far (PerlMonks - Call subroutine by reference ?) I seem to be calling the function correctly.

I've looked at this question as well, but my PrintInstruction function isn't defined in a module, it's defined locally, so if I understand correctly I don't need the $ symbol in the reference.

What am I missing?

Upvotes: 0

Views: 310

Answers (2)

ikegami
ikegami

Reputation: 386551

You are passing the code ref to the original call to SolveTowerOfHanoiProblem, but not to the recursive calls.

You could simply pass the code ref.

SolveTowerOfHanoiProblem($n - 1, ..., $moveDisk);

Or could remove the need to pass the same value over and over again.

sub SolveTowerOfHanoiProblem {
    my $moveDisk = pop;

    local *helper = sub {
        my ($n, $start, $end, $extra) = @_;

        if ($n == 1) {
            $moveDisk->($n, $start, $end);
        } else {
            helper($n - 1, $start, $extra, $end);
            $moveDisk->($n, $start, $end);
            helper($n - 1, $extra, $end, $start);
        }
    };

    helper(@_);
}

Same, but using a feature introduced in Perl 5.16:

use feature qw( current_sub );

sub SolveTowerOfHanoiProblem {
    my $moveDisk = pop;

    sub {
        my ($n, $start, $end, $extra) = @_;

        if ($n == 1) {
            $moveDisk->($n, $start, $end);
        } else {
            __SUB__->($n - 1, $start, $extra, $end);
            $moveDisk->($n, $start, $end);
            __SUB__->($n - 1, $extra, $end, $start);
        }
    }->(@_);
}

Upvotes: 4

GMB
GMB

Reputation: 222652

The SolveTowerOfHanoiProblem function expects a code ref as last (fifth) argument, but you are not passing it when you invoke it in your « else » block :

SolveTowerOfHanoiProblem($n - 1, $start, $extra, $end);

Should be written as :

SolveTowerOfHanoiProblem($n - 1, $start, $extra, $end, $moveDisk);

Upvotes: 3

Related Questions