Ruby blocks in Perl

Perhaps the best feature of Ruby is blocks. Blocks as first class objects with syntactic support for easy use. A block is a closure- it maintains the envrionment it was created in even though it is called from another function. Perl has closures too, and we can use them to do powerful things.

Block usage in Ruby and Perl.

Ruby

call_with_block() do |block_arg|
  print block_arg
end
Or
call_with_block() {|block_arg| print block_arg}

Perl

call_with_block( sub { my($block_arg) = @_; print $block_arg; });

Applying blocks

Ruby

  def call_with_block
    yield 'hello'
  end
Or
  def call_with_block(&block)
    block.call 'hello'
  end

Perl

sub call_with_block { my($block) = @_;
  $block->('hello');
}

Blocks are powerful, so lets use that power to abstract out something that really sucks in Perl- error handling. Error handling is so important that it should be built into the language. Ruby has done a good job with making exceptions easy to use to handle errors.

  begin
    code_with_possible_error
  rescue
    print "exception thrown"
  else
    print "nothing wrong"
  ensure
    print "this is printed no matter what"
  end

It is easy to combine this with blocks to produce code that gracefully handles errors. Here we will extend the low level ruby MySQL driver.

class MysqlWithTransactions < Mysql
  def transaction
    query('BEGIN')
    yield # run the block
  rescue => e
    rollback
    raise e
  else
    commit
  end
end

Now running transactional code is as easy as putting it in the block

  connection.transaction do
    # execute multiple queries in a transaction
  end

We can write the same code in Perl, but we first need a similiar exception handling construct. Blocks to the rescue! In Perl, an error must be trapped inside an eval block, and the error will be placed in the $@ variable. Below we implement the equivalent to Ruby’s exception handling construct.

sub begin_rescue_else_ensure {
  my($main, $rescue, $else, $ensure) = @_;

  sub {
    eval { $main->(); };
    my $e = $@;
    if($e){ $rescue->(); }
    else { $else->(); }
    $ensure->(); 
  }
}

Now to create the transaction code. We will do this using Perl’s DBI library in a non-object oriented style where the connection is passed as a parameter instead of being implicit in the envrionment.

sub transaction { my($connection, $main) = @_;
  begin_rescue_else_ensure(
    $main,
    sub { $connection->begin_work; },
    sub { $connection->rollback; },
    { $connection->commit; },
    sub {}
  )->();
}

And now we have easy transactional queries.

transaction($connection, sub {
  # execute multiple queries in a transaction
});

© Greg Weber. All original code snippets are placed in the public domain.
Written on Sun Oct 19 21:00:00 2008.
Tagged as perl, ruby.
Send author an email.