fivemack: (spiky)
Tom Womack ([personal profile] fivemack) wrote2006-12-20 05:49 pm
Entry tags:

hair-tearing perl question

What I want: a subroutine footle such that, if you call footle(a,b) twice with the same a,b, it does nothing the second time

What I did:
use strict;
sub footle
{
  my ($a,$b,%done) = @_;
  my $concat = $a.$b;
  if ($done{$concat} == 0)
  {
    print "footling $a $b";
    $done{$concat} = 1;
  }
}

my %isdone = ();

footle("bootle","bumtrinket",%isdone);
footle("bootle","bumtrinket",%isdone);

But this doesn't work because parameters are passed by value.

But if I call as footle("bootle","bumtrinket",\%isdone), which passes isdone by reference, it still does the footling twice.

Even if I put $_[2]=%done before the end of the subroutine, it still does the footling twice.

And if I put print join "*",(keys %done); at the start of the subroutine, it says HASH(0x8188110)footling bootle bumtrinket

So how do I really pass the parameter by reference, as if I'd said void footle(int a, int b, set<string>& done) in C++?

[identity profile] hsenag.livejournal.com 2006-12-20 06:24 pm (UTC)(link)
If you're passing it by reference, you need to dereference it when using it. "References" in Perl are more like pointers in C/C++ in the sense that they don't magically dereference themselves when used.

Completely untested:

sub footle
{
my ($a,$b,$done) = @_;
my $concat = $a.$b;
if (!exists $done->{$concat})
{
print "footling $a $b";
$done->{$concat} = 1;
}
}

my %isdone = ();

footle("bootle","bumtrinket",\%isdone);
footle("bootle","bumtrinket",\%isdone);
diffrentcolours: (Default)

[personal profile] diffrentcolours 2006-12-20 09:43 pm (UTC)(link)
What he said, though I'd use $done->{$a}{$b} = 1 to avoid issues where you call footle("foo", "bar") and then footle("fo", "obar").

There is also a Perl module which lets you mark a function as cacheable, which means that when it is called a second time with the same parameters, it returns the result from the first call without recalculating. However, the name excapes me at the moment...
diffrentcolours: (Default)

[personal profile] diffrentcolours 2006-12-20 09:46 pm (UTC)(link)
Ah, it's Memoize.pm. I knew it began with an M.

[identity profile] fivemack.livejournal.com 2006-12-21 12:35 am (UTC)(link)
That seems slightly the wrong way to look at this problem; somehow I think of memoisation as an optimisation for functions without side-effects, I wouldn't expect a memoisation module to guarantee that a function which did have side-effects caused those side-effects precisely once.

(eg I would view as totally sensible an implementation of Memoize.pm which used a small LRU cache of results, and recomputed if the input you gave it wasn't in the cache either because it had never been there or because it had dropped out; this wouldn't work in my case)

[identity profile] dd-b.livejournal.com 2006-12-20 06:33 pm (UTC)(link)
One solution, perhaps not so elegant, is to use a global or local variable (but not a "my" I think) for %done. (C static, I forget how you do that in Perl offhand).

But there's something funny going on, because it *is* possible to pass a hash reference that you update, I've done it frequently I thought.

Maybe it's a syntax problem referring to the reference; I can't find an example where a hash reference is passed in, but when I dig one out of a structure in my working applications I see code like "if (keys(%{$picdb->{DBINFO}}))", that is, %{}.

Possibly using the right function template would help? (if only to make the calling sequence cleaner).

[identity profile] fivemack.livejournal.com 2006-12-21 12:45 am (UTC)(link)
Function templates? I didn't think perl had them ... it'd be wonderful if there was some way of getting compile-time or run-time errors, rather than wrong behaviour, if with footle as [livejournal.com profile] hsenag had it, I did


def wurble
{
my ($a,$b,$c,$done) = @_;
footle($a,$b,$done);
footle($a,$c,$done);
footle($c,$b,$done);
}


and then called wurble("left","right","middle",%frog) rather than wurble("left","right","middle",\%frog).

Googling on 'perl function template' gives me a rather sophisticated way of generating stereotyped functions of the form


*$fname = sub { print join "*",$fname,@_ }

but that isn't really what I want.

[identity profile] dd-b.livejournal.com 2006-12-21 01:15 am (UTC)(link)
The term I wanted was "prototypes"; see man perlsub, and while you look at the prototypes section, you might also want to check the "pass by reference" section and the "persistent local variables" section.

closure

[identity profile] http://the.earth.li/~alex/halley/ (from livejournal.com) 2006-12-20 08:37 pm (UTC)(link)

Or you could use a closure and do away with the isdone:

{
    my %c;
    sub foo {
        if ($c{$_[0]}{$_[1]}++) {
            print "$_[0] $_[1] already!\n";
            return;
        }
        print "$_[0] $_[1]\n";
    }
    sub clear { %c = () } # to reset the cache
}

foo("badger", "ferret");
foo("badger", "ferret");

which also avoids the minor bug you had if suffixes of A and prefixes of B could get mixed up.

Re: closure

[identity profile] fivemack.livejournal.com 2006-12-21 12:40 am (UTC)(link)
Cool, I didn't know about that feature. I'm not sure whether extensions to my program might want to see %done more explicitly, so I don't think I'll do it that way, but it's a really nifty trick!

[in my app, $a and $b are element symbols, so I use $a."-".$b as the hash key and the suffix/prefix issue doesn't arise]

Re: closure

[identity profile] tau-iota-mu-c.livejournal.com 2006-12-21 02:21 am (UTC)(link)
Except if you call with:

("foo-bar", "quux") vs ("foo", "bar-quux").

I'm just succeeding in getting my own head around perl objects (POOP == best programming paradigm name EVAR!), and I just looked through my code to find the single worst line I could find (I'm sure there's more).

my $x_err = $pivot[$i]->{its}[-1]->x_err;

Calling a method of an object which returns a (reference to? I can't remember) array, which is then indexed to get the last element, which is in turn an object, so we call a method to obtain a value from it.