From Ruby To Perl

Ovid on 2008-05-21T08:17:19

I've spent a bit of time translating a small Ruby program to Perl. If you've not actually programmed in Ruby before, it's an interesting language and not all idioms translate well to Perl. There are no strictures, so variables pop into existence upon first use. Hope you don't misspell them!

Curiously, though, that seems to eliminate one (minor) nit I've always had: the declaration of recursive anonymous functions. Consider this Perl:

my $fact = sub {
    my $num = shift;
    return $num < 2 ? 1 : $num * $fact->($num - 1);
};

That doesn't actually work because $fact doesn't exist at the time you call it. So you need to do this:

my $fact;
$fact = sub {
    my $num = shift;
    return $num < 2 ? 1 : $num * $fact->($num - 1);
};

Now, depending on whether or not you like the ternary operator, this is straightforward, idiomatic Perl. It's also just a tad ugly, having to declare that variable before you can use it. Here's the Ruby equivalent:

fact = proc { |num|
    return num < 2 ? 1 : num * fact[num - 1]
}

Also, I liked this really nifty local redefining of the << operator (typically this is the Ruby equivalent of 'push'):

def <<(rhs)
  case rhs
    when Array
      fact(*rhs)
    else
      fact(rhs)
  end
end

While without context it doesn't mean much, it does simplify some of the code interacting with the object this is redefined for.

Now I'm tangling with the 'yield' keyword. I know what it does, but translating it idiomatically to Perl is interesting.

Any Ruby equivalent of perldoc -f?


See?

Aristotle on 2008-05-21T09:07:21

That doesn’t actually work because $fact doesn’t exist at the time you call it.

So you simply use the Y combinator. :-)

Re:See?

Ovid on 2008-05-21T09:38:16

I had thought of that when I was writing this, but I confess that I never really understood why people were so hot and bothered about the Y combinator. There's a point when complexity overrides functionality. That being said, it may simply be a case of my being dense, but an awful lot of smart people seem to struggle with this :)

Re:See?

Aristotle on 2008-05-21T19:29:28

Complexity of what? The Y combinator is easy to use: you return the recursive closure from another closure; the first argument of the outer closure can be used in the inner closure to return. That’s it.

The difficulty is in deriving the exact form of the combinator (which, of course, is a bit of brain-melt, which is why it’s become a sort of sport), not in using it.

Re:See?

Aristotle on 2008-05-22T05:19:25

Err, the first argument of the outer closure can be used in the inner closure to recurse.

You saw rubyisms?

Simon on 2008-05-21T09:43:07

rubyisms.pm has an idiomatic translation of "yield" (As well as "self" and "super"). As for perldoc -f, ri should do everything you want.

Re:You saw rubyisms?

Ovid on 2008-05-21T09:58:17

I'm working with the 'yield' function from that now. And thanks for the tip about 'ri'. Very useful!

Re:You saw rubyisms?

Ovid on 2008-05-21T10:22:37

Actually, I can't seem to use your 'yield' as a direct translation. I'm missing something fundamental. I put together this in Ruby (very similar to my actual problem):

class Foo
    attr_reader :val
    def initialize(val)
        @val = val
    end
    def inc(some_val)
        @val = @val + some_val
    end
end

def testy(args)
    foo = Foo.new(1)
    _testy(args, foo) {
        yield foo
    }
end

def _testy(args,somefoo)
    for item in args
        somefoo.inc(item)
        yield
    end
end

testy([1,2,3]) {|x| puts x.val}

That, as expected, prints 2, 4 and 7. However, attempting to translate that to Perl is not only exceedingly clumsy, it also doesn't seem to work and I can't figure out why.

#!/usr/bin/env perl

use strict;
use warnings;

use rubyisms;

package Foo;
sub new { bless { val => $_[1] } => $_[0] }
sub inc { $_[0]->{val} += $_[1] }
sub val { shift->{val} }
package main;

sub _testy(&@);

sub testy (&@) {
    my ( undef, $args ) = @_;
    my $foo  = Foo->new(1);
    _testy { yield $foo } ($args, $foo);   # line 19
}

sub _testy (&@) {
    my ( undef, $args, $somefoo ) = @_;
    for (@$args) {
        $somefoo->inc($_);
        yield;
    }
}

testy { print shift->val, $/ } ([1,2,3]);
__END__
no block given (LocalJumpError) at yield.pl line 19

Sub::Current for the Win!

Stevan on 2008-05-21T14:34:41

See, this is exactly why I love Perl, cause there is always a something on CPAN that will make your life easier.

use Sub::Current;

print sub {
    my $num = shift;
    return $num < 2 ? 1 : $num * ROUTINE->($num - 1);
}->(10);

- Stevan

Re:Sub::Current for the Win!

Ovid on 2008-05-21T14:39:29

That's great, but solve my 'yield' problem and I'll really love you :)

Re:Sub::Current for the Win!

Stevan on 2008-05-21T23:33:36

Well, all the "yield" and "block" stuff is just sugar around your basic continuation passing. So here is the translation of what the ruby is actually doing.

#!/usr/bin/perl

use strict;
use warnings;

{
    package Foo;
    sub new { bless { val => $_[1] } => $_[0] }
    sub inc { $_[0]->{val} += $_[1] }
    sub val { shift->{val} }
}

sub testy {
    my ($args, $cont) = @_;
    my $foo = Foo->new(1);
    _testy($args, $foo, sub { $cont->($foo) });
}

sub _testy {
    my ($args, $somefoo, $cont) = @_;
    foreach my $item (@$args) {
        $somefoo->inc($item);
        $cont->();
    }
}

testy([1,2,3], sub { print $_[0]->val, $/ });

1;

I tried wrapping some sugar around this, but it gets really messy with scoping. I suspect to do it right, you would need to do what Ruby does and make the subs into objects (Proc is what it is called in Ruby IIRC) and then wrap the sugar around that. Autobox might help here too, if for nothing more than removing the need to actually bless the subs.

- Stevan

Re:Sub::Current for the Win!

Stevan on 2008-05-22T00:53:47

I took the dog for a walk and thought about this a bit and realized where my scoping issue was. Here is a sugared version, probably could be a little cleaner still, maybe even with some subroutine attributes (sub foo : continuation { ... } or something).

#!/usr/bin/perl

use strict;
use warnings;
use Scalar::Util 'blessed';

{
    package Foo;
    sub new { bless { val => $_[1] } => $_[0] }
    sub inc { $_[0]->{val} += $_[1] }
    sub val { shift->{val} }
}

our $current_continuation = sub {};
sub yield { $current_continuation->(@_) }
sub proc (&) {
    my $block = shift;
    my $cont  = $current_continuation;
    bless sub {
        local $current_continuation = $cont;
        $block->(@_)
    } => 'proc';
}
sub func {
    my ($name, $body) = @_;
    no strict 'refs';
    *{$name} = sub {
        local $current_continuation = pop @_
            if blessed $_[-1] && $_[-1]->isa('proc');
        $body->(@_);
    };
}

func testy => sub {
    my ($args) = @_;
    my $foo = Foo->new(1);
    _testy($args, $foo, proc { yield($foo) });
};

func _testy => sub {
    my ($args, $somefoo) = @_;
    foreach my $item (@$args) {
        $somefoo->inc($item);
        yield();
    }
};

testy([1,2,3], proc { print $_[0]->val, $/ });

1;

- Stevan

Re:Sub::Current for the Win!

Ovid on 2008-05-22T04:11:49

Damn. I am doing the bit with passing around the sub, but it really does read like what you've done is much cleaner and actually makes it read like Ruby.

Re:Sub::Current for the Win!

Stevan on 2008-05-22T18:36:12

Actually took a look into rubyisms, I think that is just another case of Cozens "egoware" (only one version ever released and not meant to actually be used, only for people to look at and go "wow"). All the DB:: fiddling means it is bound to be very fragile with things like eval, etc.

RE: reading like Ruby

I actually find the desugared perl one to be the most readable since it doesn't obscure what is actually being done.

- Stevan

yieldness

awwaiid on 2008-05-24T03:47:58

Ruby's use of the 'yield' keyword caught me off guard. I suppose I'm used to how python uses it, as part of generators (rather than as simply a sub invocation). Well... here's how you might do something similar with Coro::Generator anyway :)

use strict;
use Coro::Generator;

{
  package Foo;
  sub new { bless { val => $_[1] } => $_[0] }
  sub inc { $_[0]->{val} += $_[1] }
  sub val { shift->{val} }
}

my $inner_testy = generator {
  my $foo = pop;
  foreach my $item (@_) {
    $foo->inc($item);
    yield($foo);
  }
  yield undef;
};

sub testy {
  my $action = pop;
  my $foo = Foo->new(1);
  $action->($foo) while $inner_testy->( @_, $foo );
}

testy(1,2,3, sub { print $_[0]->val . "\n" });