UNIVERSAL::can なんてあるのね

| コメント(2) | トラックバック(0)
A というすでにいろんなメソッドが定義されているクラスがあって、その中に foo っていうメソッドがすでにあったとします。で、その A というクラスを継承した B というクラスを作ったとして、でもあるプログラムで B のインスタンスを生成して $b->can('foo') ってやったときだけ真にならない(他のメソッドが呼ばれたときは真にする)方法ってないかということで先日 id:nipotanや id:ikebe と話をしていたところ、UNIVERSAL::can なるものをみつけた。これを使えばやりたいことはできるのではという話になりました。

UNIVERSAL::can はなにをするもんかというと、それを use したクラスに can というメソッドを定義すれば、そのクラスのインスタンスに対して $instance->can('hoge') ってやると、そのクラスに定義した can が使われるというものだそうで、これを上手く上記の問題に照らし合わせると。。。

B というクラスで、
sub can { $_[1] eq 'foo' ? undef : UNIVERSAL::can($_[0], $_[1]) }
という風に can を定義しておけば、やりたいことができるんじゃないかと。
テストコードの全容は下記のような感じです。これを実行すると $b->can('foo') は false なので sub foo { warn "foo" } は実行されず、$b->can('bar') はコードリファレンスが返されるので sub bar { warn "bar" } が実行されます。

#!/usr/local/bin/perl
use strict;

package a;

sub new { bless {}, shift }
sub foo { warn "foo" }
sub bar { warn "bar" }

package b;

use base qw(a);
use UNIVERSAL::can;

sub can { $_[1] eq 'foo' ? undef : UNIVERSAL::can($_[0], $_[1]) }

package main;

my $b = b->new;
$b->can('foo') && $b->can('foo')->();
$b->can('bar') && $b->can('bar')->();
これ他にやりかたあるんだろうか…。ちなみに、普通の can は perldoc UNIVERSAL とやると説明が書いてあるというのも初めて知りました。。。
追記
コメントにありますが、当初やりたかったことは B のクラスに sub can {} を定義してやれば $b->can() って呼んでやるのであれば解決するという結論になりました。UNIVERSAL::can() を使う場合には UNIVERSAL::can モジュールを use してやる必要があるようです。

トラックバック(0)

トラックバックURL: http://blog.clouder.jp/mt/mt-tb.cgi/631

コメント(2)

ん、なんか違う気がする。

sub can を定義すれば変えられるのは、UNIVERSAL::can 使わなくても元からそうですよ。

package A;
sub foo { warn "foo" }

package B;
@ISA = qw(A);

sub can { return }

$b = bless {}, "B";
print $b->can("foo") ? "can\n" : "can't\n";


んで、can をオーバーライドすればいいんだけど、これって if ($foo->can("bar") じゃなくて UNIVERSAL::can($foo, "bar") って呼んじゃうと自前の sub can が呼ばれないので、それを何とかHackしてやろうってのが、UNIVERSAL::can モジュールなんじゃないかと。

なるほど。
UNIVERSAL::can は、自前の can を UNVERSAL::can を使っても呼ばれるように Hack するためのものなんですね。。。

ってことで元々やりたかったことは UNIVERSAL::can を使わなくても sub can {} を定義してやればできるってことか。勉強になりますた。

コメントする

検索

広告

月別 アーカイブ

OpenID対応しています OpenIDについて
Powered by Movable Type 5.12