http://www.perlmonks.org?node_id=256648

mvaline has asked for the wisdom of the Perl Monks concerning the following question:

I'm working through the wonderful textbook Structure and Interpretation of Computer Programs and am trying to use some of what I'm learning in Perl instead of LISP.

One of the powerful things that LISP can do is accept procedures as arguments. This gives the programmer a lot of flexibility. The example given in SICP is that of the summation (think sigma notation). Using LISP, it is possible to model summation independently of the particular series being summed. Instead of writing individual summation procedures like this:

(define (sum-integers a b)   (if (> a b)       0       (+ a (sum-integers (+ a 1) b))))

and this:

(define (sum-cubes a b)   (if (> a b)       0       (+ (cube a) (sum-cubes (+ a 1) b))))

You can write something like this:

(define (sum term a next b)   (if (> a b)       0       (+ (term a)          (sum term (next a) next b))))

and use it like this:

(define (inc n) (+ n 1)) (define (sum-cubes a b)   (sum cube a inc b))

Now I easily write summation procedures in Perl like this:

sub sum_integers { my ($a, $b) = @_; if ($a > $b) { return(0); } else { return( $a + &sum_integers(($a + 1), $b) ); } } sub cube { my ($a) = @_; return( $a * $a * $a ); } sub sum_cubes { my ($a, $b) = @_; if ($a > $b) { return(0); } else { return( &cube($a) + &sum_cubes(($a + 1), $b) ); } }

But what I want to do is something like this:

sub sum { my ($term, $a, $next, $b) = @_; if ($a > $b) { return(0); } else { return( $term($a) + &sum( $term, ($next($a)), ($next($b)) ) + ); } } sub inc { my ($n) = @_; return( $n + 1 ); } sub sum_cubes2 { my ($a, $b) = @_; return( &sum(&cube, $a, &inc, $b) ); }

But I can't figure out how to use a subroutine once I pass it to another subroutine. I know I'm passing the address of a subroutine using the &subroutine construct, but how do I run it when the reference to it is being stored in a scalar variable (i.e. the $term and $next variables in the sum subroutine above) ?

Replies are listed 'Best First'.
Re: Passing subroutines as arguments
by chip (Curate) on May 08, 2003 at 19:45 UTC
    For a good explanation of the theory, read perldoc perlref. In the meantime:
    sub call_twice { my $func = shift; &$func("first"); $func->("second"); } sub foo { print "foo: @_\n" } call_twice(\&foo); call_twice(sub { print "bar: @_\n" });

        -- Chip Salzenberg, Free-Floating Agent of Chaos

      Interesting... So what's wrong with this? sum_cubes2(1, 3) is returning 1 instead of 36 like it should and giving the warning "Use of uninitialized value in numeric gt (>) at /Users/mvaline/bin/summation line 35."
      sub sum { my ($term, $a, $next, $b) = @_; if ($a > $b) { return(0); } else { return( $term->($a) + &sum( $term, ($next->($a)), ($next->( +$b)) ) ); } } sub inc { my ($n) = @_; return( $n + 1 ); } sub sum_cubes2 { my ($a, $b) = @_; return( &sum(\&cube, $a, \&inc, $b) ); }

        Read perldiag for an explanation of the error message; the error message does not lie, you're attempting to compare values, at least one of which is undefined. There's only one comparison in the code you post, so assuming that's the "offending" line, the problem is that one of $a and $b is not initialized in the call to sum. Why that would be so depends on how that subroutine came to be called, and if you look at the else clause of your conditional in the sum subroutine, you'll see the problem; what you pass to the call to sum there are three things: $term, and the results of calling the sub it points to on $a and $b. Yet your subroutine's ... ummm... signature calls for there to be four objects, so the result is that when that recursive call is made, $b is undefined.

        HTH

        If not P, what? Q maybe?
        "Sidney Morgenbesser"

Re: Passing subroutines as arguments
by dws (Chancellor) on May 08, 2003 at 19:46 UTC
    But I can't figure out how to use a subroutine once I pass it to another subroutine.

    Assuming that you have a reference to a subroutine in $sub,

    &$sub(); or $sub->();
    will invoke that subroutine without arguments, and
    &$sub(1, 2, "grapefruit"); or $sub->(1, 2, "grapefruit");
    will invoke it with arguments.

    Which form you choose is a matter of style.

      Which form you choose is a matter of style. Uhh - not quite. &FOO and FOO() do different things with @_ and barewords. While the latter isn't a concern here, the former might be. YMMV.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

        Uhh - not quite. &FOO and FOO() do different things with @_ and barewords.

        Noting that &FOO and FOO() behave differently is fine, but please don't use the pretense of objecting to something that I didn't write. I didn't (and don't) recommend the &FOO form except under very contorted circumstances.

        In Response to dragonchild's post:
        I think in this instance you may have wanted to post how they behave differently, as the peson who posted the question is obviously not going to realize this difference.

        When one criticizes another's reply to a post, it is considered good manners to explain oneself fully instead of giving very terse comments. As this behaviour could be misconstrued to be argumentative and counterproductive.

Re: Passing subroutines as arguments
by dpuu (Chaplain) on May 08, 2003 at 19:47 UTC
    The arrow operator:
    sub a { my ($sub) = @_; $sub->(4) } sub b { print @_ }; a(\&b);
    If you're trying to be lisp-like, you probably also want to look into closures:
    a( sub {print @_} )
    --Dave
Re: Passing subroutines as arguments
by hv (Prior) on May 08, 2003 at 19:56 UTC

    You need to dereference the reference. Just as when $a contains an array reference, @$a will dereference it to give the array, so when $c contains a code reference, &$c will dereference it to call the code. To pass arguments, you just add a parenthesised list in the normal fashion:

    sub sum { my($term, $a, $next, $b) = @_; return 0 if $a > $b; return &$term($a) + sum($term, &$next($a), $next, $b); }

    You can also use the dereferencing arrow, in which case the parens are required:

    sub sum { my($term, $a, $next, $b) = @_; return 0 if $a > $b; return $term->($a) + sum($term, $next->($a), $next, $b); }

    Hugo
Re: Passing subroutines as arguments
by dragonchild (Archbishop) on May 08, 2003 at 20:39 UTC
    Don't use $a and $b. Those should be left reserved for sort. Even though my does lexicalize them, it will cause problems if you try and sort them.

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

Sum of two cubes....
by Mr. Muskrat (Canon) on May 08, 2003 at 20:09 UTC

    Your question has already been answered so I thought I'd throw in another way (there are more) to accomplish summing two cubes...

    sub sum2cubes { my @cube = @_; # more meaningful than $a and $b return($cube[0] ** 3 + $cube[1] ** 3); } print sum2cubes(3,4);

    Simplicity is one of Perl's finer points.

Re: Passing subroutines as arguments
by @ncientgoose (Novice) on May 09, 2003 at 03:23 UTC
    You would dereference the scalar like this:
    sub sum { my ($term, $a, $next, $b) = @_; if ($a > $b) { return(0); } else { return( &$term($a) + &sum( &$term, (&$next($a)), (&$next($b +)) ) ); } }