[ABE.pm] My first enclosure doesn't work. :-(

Ricardo SIGNES rjbs-perl-abe at lists.manxome.org
Fri Oct 26 15:13:07 PDT 2007


* "Faber J. Fedor" <faber at linuxnj.com> [2007-10-26T13:30:50]
> Now what? At the end of my enclosure, I say 'return sub { $stmt };' or
> 'return sub { eval($stmt) };'. If I then do a 'print
> make_if_elsif(300)' I get 'CODE(0x8096228)' instead of the expected
> '1.0'.
> 
> What am I doing wrong?

Well, for one thing, you're even considering using string eval. :)

I'm very confused by the code you pasted, so I'm going to potentially ignore
all of it and write something vaguely related and see if it makes things any
clearer.

This code includes a routine call "make_bucketizer."  It returns a closure that
tells you what bucket something falls into.  You pass it a number $n and it
will bucketize into bins that are 10**$n wide.  In other words,
make_bucketizer(0) makes bins for 0-1, 1-2, ... 9-10.  make_bucketizer(1) makes
bins for 0-10, 10-20, ... 90-100.

You can call the return routine with a number, and it returns the index of the
bucket into which the number would fall.

It also returns a second closure, which returns a list of pairs: all the
buckets into which data were dumped, along with the data dumped there.

Here's a usage example:

  my ($bucketize, $get_buckets) = make_bucketizer(2);

  $bucketize->(10);   # in bucket 0, 0 - 100
  $bucketize->(5020); # in bucket 50, 5000 - 5100
  $bucketize->(1852); # in bucket 18, 1800 - 1900
  $bucketize->(82);   # in bucket 0, 0 - 100

  my %content = $get_buckets->();

  Dump(\%content);

Produces:

  my $content = {
    0  => [ 10, 82 ],
    18 => [ 1852 ],
    50 => [ 5020 ],
  };

This code does *not* dynamically generate an if/else tree.  Basically, in
dynamic Perl, you Just Don't Do That.  if/else trees are literal structures,
and you'd have to generate code and then eval it.  This is a red flag the size
of Texas.  Instead, you'd build a data structure that can serve as a kind of
dispatch table, or you eliminate the need for lookup at all by being highly
generic.  I'll provide two examples:

  # This make_bucketizer takes $n and returns a routine that can bucketize
  # arbitrary-sized numbers.
  sub make_bucketizer {
    my ($n) = @_;

    my $divisor = 10 ** $n;
    my %bucket;

    my $bucketizer = sub {
      my ($x) = @_;

      die "no negative numbers!" if $x < 0;

      # Note that buckets are [ m, n ) -- the zeroth bucket for $n = 0, for
      # example, includes 0 and runs up to anything less than 1, exclusive.
      my $bucket_number = int($x / $divisor);

      my $this_bucket = $bucket{ $bucket_number } ||= [];

      push @$this_bucket, $x;
    };

    my $get_buckets = sub { %bucket };

    return ($bucketizer, $get_buckets);
  }

The example above avoids ever having to do a lookup, as it can always compute
-- it's still a legitimate use of closures, because it closes usefully over
both $n and %bucket.

Let's say you want to make a lookup table, though, because on your processor,
division is really, really slow.

  # This make_bucketizer takes $n and $num_buckets,  and returns a routine that
  # can bucketize into $num_buckets buckets.
  sub make_bucketizer {
    my ($n, $num_buckets) = @_;

    my $multiplier = 10 ** $n;

    # I would probably write this with (map BLOCK LIST) but for the sake of
    # simplicity, here I use a loop.
    my @bins;
    for my $i (0 .. $num_buckets) {
      push @bins, $multiplier * ($num_buckets + 1);
    }

    # So now @bins is a list of upper-limits.

    my %bucket;

    my $bucketizer = sub {
      my ($x) = @_;

      die "no negative numbers!" if $x < 0;

      # Note that buckets are [ m, n ) -- the zeroth bucket for $n = 0, for
      # example, includes 0 and runs up to anything less than 1, exclusive.
      my $bucket_number;
      BIN: for my $i (0 .. $#$bins) {
        next BIN if $x >= $bins[$i];
        $bucket_number = $i;
        last BIN;
      }

      die "$x too large to bucketize" unless defined $bucket_number;

      my $this_bucket = $bucket{ $bucket_number } ||= [];

      push @$this_bucket, $x;
    };

    my $get_buckets = sub { %bucket };

    return ($bucketizer, $get_buckets);
  }

It would be pretty easy to adapt this code to not take ($n) as a power of ten,
but to take (%buckets) as a map of max values to buckets or (@buckets) as a
list of pairs.

-- 
rjbs


More information about the ABE-pm mailing list