asin:4873113148
PERL HACKS
(日本語版)
[英語版]

うーん、さすがにそれはいいすぎでしょうか。

クロージャの概念をクラスとの対比でわかりやすく説明する。 - サンプルコードによる Perl 入門
ここで気づいてほしいことは、クラスとクロージャは、実は同じものだということです。

たしかにオブジェクトの定義から行くと、

404 Blog Not Found:オブジェクトは難しくない。難しいのはクラス
それで、オブジェクトとは何か、といえば、「自分が何が出来るのか[コード]を知っているデータ」ということになる。

であり、クロージャー(closure)は「自分が何を持っているか[データ]を知っているコード」なので、等価ではある。実際、

package Class;
sub new { 
    my $pkg = shift; 
    bless {@_}, $pkg
}
sub property {
    my $self = shift;
    return $self->{property} unless @_;
    $self->{property} = shift;
}

は、以下のようにしてclosureにやらせることも出来る。

package Closure;
sub new{
    my $instance = { @_ };
    return sub{
        my $property = shift;
        return $instance->{$property} unless @_;
        $instance->{$property} = shift;
    }
}

実際にその様子を確認してみよう。

Run via Codepad
my $o = Class->new;
$o->property('dan');
warn $o->property;
my $c = Closure::new;
$c->(property => 'kogai');
warn $c->('property');

Class->newClosure::newに、$o->property()$c->('property')に書き直せば、見事に一対一対応する。

しかし、実際の運用では、こうなる。

new
            Rate Closure   Class
Closure 167276/s      --    -68%
Class   529717/s    217%      --
store
            Rate Closure   Class
Closure 967909/s      --     -1%
Class   977938/s      1%      --
fetch
            Rate Closure   Class
Closure 945327/s      --     -0%
Class   948318/s      0%      --

一端生成してしまえば通常、すなわちクラスベースのオブジェクトと遜色のないクロージャーだが、その生成コストはperlにおいては比較的高い。すなわちより多くの時間とメモリーを消費する。(ほぼ)完全なカプセル化という利点がありつつも、Perlにおいてオブジェクトの実装にクロージャーが用いられていない理由の一つがこれだ。ちなみにLispではクロージャーの生成コストは「ただのS式」の生成コストとほぼ同じということもあってか、他がOOでやることをクロージャーでやるのが一般的だったりする。

ちなみに、(ほぼ)完全なカプセル化を実装するもう一つの方法として、Inside-Out Objectという手法が存在している。本blogでも

で紹介しているのであわせて参照のこと。こちらの方が速度面では有利である、ただし通常のハッシュリファレンスによる実装よりは低速であり、そして簡単にシリアライズできないという欠点がある(カプセル化という点ではこれはむしろ利点ではあるが。Mooseで生成したオブジェクトも、「見た目」は単なるblessされたハッシュリファレンスだ。

ちなみに、Perlではリファレンスはblessさえすればオブジェクトとなるので、「クロージャーかつオブジェクト」というものが存在する。拙作Scalar::Lazyは、これを活用している。

というわけで結論。

  1. クロージャーにはクラスと同じことが出来る
  2. ただし、Perlにおいてはそのコストは比較的割高である。

ところで、

クロージャの概念をクラスとの対比でわかりやすく説明する。 - サンプルコードによる Perl 入門
クロージャは、継承できない。クラスのように継承をすることはできません。

ところが、できるんだなこれが。宿題にしときます。

Dan the Perl Monger

#!perl
use strict;
use warnings;
{
    package Class;
    sub new { 
        my $pkg = shift; 
        bless {@_}, $pkg
    }
    sub property {
        my $self = shift;
        return $self->{property} unless @_;
        $self->{property} = shift;
    }
}
{
    package Closure;
    sub new{
        my $instance = { @_ };
        return sub{
            my $property = shift;
            return $instance->{$property} unless @_;
            $instance->{$property} = shift;
        }
    }
}

{
    my $o = Class->new;
    $o->property('dan');
    warn $o->property;
    my $c = Closure::new;
    $c->(property => 'kogai');
    warn $c->('property');
}

use Benchmark qw/timethese cmpthese/;
my ($o, $c);

cmpthese(
    timethese(
        0,
        {
            Class   => sub { $o = Class->new },
            Closure => sub { $c = Closure::new }
        }
    )
);
cmpthese(
    timethese(
        0,
        {
            Class   => sub { $o->property(1) },
            Closure => sub { $c->( property => 1 ) }
        }
    )
);
cmpthese(
    timethese(
        0,
        {
            Class   => sub { $o->property == 1     or die },
            Closure => sub { $c->('property') == 1 or die }
        }
    )
);