http://qs1969.pair.com?node_id=384885

How many of you have ever thought about how to implement coroutines in Perl?

Wow, I hadn't expected any. How many of you with your hands raised have thought about how you might avoid source filters and evil gotos?

Ok, with the exception of perhaps TheDamian, how many actually tried it? It seemed simple enough and I had a working solution in about 10 minutes:

  • Break the code between the yields into code refs
  • Create a dispatch table that knew about all the sections
  • Create a tied variable that knows how to cycle through the dispatch table
  • Return a closure calling the dispatch table

The problem was that it was fugly and no longer resembled a coroutine. I figured if I put it inside a module (see below), it would hide all the nasty stuff and maybe even make it more useable.

package Iterator; sub TIESCALAR { bless $_[1], $_[0] } sub STORE {} sub FETCH { $_[0]->[1] = 0 if ! $_[0]->[1] || $_[0]->[1] == @{ $_[0]-> +[2] }; $_[0]->[1]++ } package Coroutine; sub new { my $self = bless [[[]]], $_[0]; tie $self->[0][0], 'Iterator', $self->[0]; return $self; } sub add_section { push @{ $_[0]->[0][2] } , $_[1] } sub create { my $s = shift; return sub { $s->[0][2][ $s->[0][0] ]->($s +, @_) } } 42;

Boy was I wrong! It came out horrendous. What, you don't believe me? Just look:

#!/usr/bin/perl use strict; use warnings; use Coroutine; my $coroutine = Coroutine->new(); $coroutine->add_section( sub { my $self = shift; $self->[3] = shift; $self->[4] = \@_; print "$_\n" for @{ $self->[4] }; return $self->[3]++; } ); $coroutine->add_section( sub { my $self = shift; print "$self->[3]\n"; return rand() > .5 ? 'weird' : ++$self->[3]; } ); $coroutine->add_section( sub { my $self = shift; print "The end is near - goodbye cruel "; return pop @{ $self->[4] }; } ); my $wacky = $coroutine->create(42, 'hello', 'world'); print $wacky->(42, 'hello', 'world'), "\n"; print $wacky->(), "\n"; print $wacky->(), "\n"; print $wacky->(), "\n";

All of that to do the following if Perl natively supported coroutines:

coroutine create { my $foo = shift; my @bar = @_; print "$_\n" for @bar; yield $foo++; print "$foo\n"; yield rand() > .5 ? 'weird' : ++$foo; print "The end is near - goodbye cruel "; yield pop @bar; } my $wacky = create(42, 'hello', 'world'); print $wacky->(42, 'hello', 'world'), "\n"; print $wacky->(), "\n"; print $wacky->(), "\n"; print $wacky->(), "\n"; __END__ hello world 42 43 44|weird The end is near - goodbye cruel world 0

Incidently, revdiablo and I came up with a solution using evil gotos and source filters about a week ago that was elegant.

So why post? Well even with the explanation, it isn't easy to see what is going on inside the module - especially with the bless/tie combo. I really didn't intend it to come out obfu. Sometimes obfu just happens

Cheers - L~R

I originally posted this under Obfuscation, but didn't argue with Enlil when he asked to move it here. The real meditation, "Sometimes obfu just happens" along with "sometimes breaking the rules is the best way to do things" is there though - along with an attempt at some humor ;-)