Slash Boxes
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

drhyde (1683)

  (email not shown publicly)

Journal of drhyde (1683)

Saturday February 23, 2008
10:16 AM

How useful closures are!

[ #35732 ]

As some of you may be aware, I'm working on a pure-perl Z80 emulator. Why? <shrug> Why not! And we can't go letting the 6502 boys have all the fun!

It also makes a very good example of how useful closures can be. Here's just one example.

The Z80 is an 8 bit processor, with several 8 bit registers. But it also has some 16 bit "reigster pairs", which are made up of two 8 bit registers that can be treated as a single larger register. For example, the 8 bit register B and C can be combined to make a 16 bit register BC. Any operation that changes B or C also changes BC, and vice versa. Now, to implement them, I could have lots of hairy logic so that any instruction that operates on BC actually operates on both B and C. But that would lead to lots of duplication of code, for all the register pairs, and for all the instructions that operate on 'em.

I already have a ::Register8 class for an 8 bit register, and a Register16 class for real 16 bit registers such as the Program Counter (a register that points at the next instruction to execute) which store a value and have get() and set() methods. So, I thought, instead of putting lots of hairy logic in the instructions themselves, I could move it into the registers so I'd only have to write it once. The Register* classes already have a 'value' field for storing the register's current value. So I changed Register16 to have either a value field or a pair of 'get' and 'set' fields, depending on how the register was initialised in perl. If it has a value field, then get() and set() simply operate on that. But if instead it has get/set fields, then get() and set() call those code-refs instead.

So now, I can create a 16 bit register pair like this ...

my $BC = CPU::Emulator::Z80::Register16->new(
  get => sub {
    return 256 * $self->register('B')->get() +
  set => sub {
    my $value = shift;
    $self->register('B')->set($value >>8);
    $self->register('C')->set($value & 0xFF);

and then as far as implementing the actual Z80 instructions goes, I can just get() and set() sixteen bit registers with a single method call, instead of doing all the multiplication, shifting, bitmasking and so on every time. Note that I use a variable $self inside the two anonymous subroutines without declaring it with my. This isn't a bug. That is the $self in the code that creates the anonysubs. It has nothing to do with whatever $self might be inside a Register16 class's get() or set() method.

But there are actually several register-pairs, so I went one step further. Instead of repeating that chunk of code several times, once for each pair, I did this:

$AF = _derive_register16($self, qw(A F));
$BC = _derive_register16($self, qw(B C));
$DE = _derive_register16($self, qw(D E));


sub _derive_register16 {
  my($self, $high, $low) = @_;
  return CPU::Emulator::Z80::Register16->new(
    get => sub {
      return 256 * $self->register($high)->get() +
    set => sub {
      my $value = shift;
      $self->register($high)->set($value >>8);
      $self->register($low)->set($value & 0xFF);

In this case, I pass $self and the names of two 8 bit registers to the _derive_register16 function. That function then creates and returns a Register16 now "closing over" 3 variables.

Closures rock. By combining objects and closures I have ended up writing a lot less code. And by reducing the amount of duplication in my code, when I inevitably find a bug, I'll have just one place to fix it, instead of having to remember all the hundred places where I twiddle register pairs. If you ever find yourself writing the same code over and over again just with minimally different data, this is a useful technique.

You can see what I'm up to on this project by looking at the CVS repository.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
More | Login | Reply
Loading... please wait.