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() + $self->register('C')->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() + $self->register($low)->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.
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: Mmmm, closures, they're almost as nice as bacon
by vrk (Chaplain) on Feb 23, 2008 at 22:11 UTC | |
Re: Mmmm, closures, they're almost as nice as bacon
by spurperl (Priest) on Feb 24, 2008 at 19:48 UTC | |
by DrHyde (Prior) on Feb 25, 2008 at 11:09 UTC | |
Re: Mmmm, closures, they're almost as nice as bacon
by locked_user sundialsvc4 (Abbot) on Feb 25, 2008 at 15:51 UTC |