Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw

Mmmm, closures, they're almost as nice as bacon

by DrHyde (Prior)
on Feb 23, 2008 at 15:33 UTC ( #669752=perlmeditation: print w/replies, xml ) Need Help??

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

    At work, I have to program in Java. (Since I'm a Perl programmer at heart, this is obviously not a stable job.) If I scratched a mark on my desk every time I encountered a situation where a closure were the simplest way to implement something and I could not use it (since Java doesn't have the concept), there would be only splinters left. You can simulate closures in some cases by using anonymous inner classes and instantiating anonymous classes from (possibly private) static interfaces, but it looks horrible and you only get half of the benefit.

    Closures are incredibly powerful. Now if we only had call-with-current-continuation in Perl, we could save not only the calling context but the call stack as well...

    print "Just Another Perl Adept\n";

Re: Mmmm, closures, they're almost as nice as bacon
by spurperl (Priest) on Feb 24, 2008 at 19:48 UTC
    And we can't go letting the 6502 boys have all the fun!
    I've had lots of fun with my pure-Perl MIX assembler and simulator as well :-)

    More seriously though, this is a nice example of closures.

      Of course, MIX is a whole machine, not just a CPU.

      Implementing a usable machine around my emulator, with I/O etc, is next on my to-do list!

Re: Mmmm, closures, they're almost as nice as bacon
by sundialsvc4 (Abbot) on Feb 25, 2008 at 15:51 UTC

    Quite honestly, I think you're thinking too much.

    Just store the register-values as convenient integers. When you need to do a 16-bit manipulation, gather the register values together, do the math, and distribute the result back into the two registers. You're building structure for the sake of structure...

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://669752]
Approved by kyle
Front-paged by Corion
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (6)
As of 2018-06-24 07:43 GMT
Find Nodes?
    Voting Booth?
    Should cpanminus be part of the standard Perl release?

    Results (126 votes). Check out past polls.