clone

かなり前にオセロみたいなゲームを作ろうと思ったことを思い出して、せっせと作り始める。
そんなわけでPerlオブジェクト指向っぽくプログラミングをしていたら、
どうしてもcloneっぽい機能がほしくなった。
正しいかわからないけど、cloneっぽく機能するメソッドを書いてみた。



実はcloneってのが何をやっているかよくわかってない。
リファレンスを芋づる式みたいに辿ってそのコピーをとるんでしょって認識で合っているのなら、
要は、参照ならその参照先もコピーすればいいってことなので、
再帰させれば簡単にできるんじゃないかな。と。

package xxx;
# 〜

# クローンメソッド
sub clone{
	my $self = shift;
	
	my $clone = bless (_recursiveClone($self), __PACKAGE__);
	
	print "clone:$self => $clone\n";
	foreach(keys %{$self}){
		print "  clone{$_}:$self->{$_} =>$clone->{$_}\n";
	}
	
	return $clone;
}

# 再帰
sub _recursiveClone{
	my $ref = shift;
	if(ref $ref eq 'HASH' || ref $ref eq __PACKAGE__){
		my %hash = ();
		foreach(keys %$ref){
			$hash{$_} = _recursiveClone($ref->{$_});
		}
		return \%hash;
	}elsif(ref $ref eq 'ARRAY'){
		my @array = ();
		foreach(@$ref){
			push(@array, _recursiveClone($_));
		}
		return \@array;
	}elsif(ref $ref eq 'SCALAR'){
		my $tmp = $$ref;
		return \$tmp;
	}elsif(ref $ref eq 'REF'){
		my $tmp = _recursiveClone($$ref);
		return \$tmp;
	}
	
	return $ref;
}


cloneメソッドのbless (_recursiveClone($self), __PACKAGE__);ってところは、
表示確認のためにblessしたものを一回受け取ってるだけなので、そのままreturnしても問題ないはず。
_recursiveCloneは受け取った変数が何のリファレンスか調べて、
決まったリファレンスであれば、それぞれにあったデリファレンスをしていき、
さらに_recursiveCloneに放り込み、最終的にリファレンスを返す。
ただ、スカラーのリファレンスだけはデリファレンスした後、代入した変数の参照を返す。
リストかハッシュかスカラーのリファレンスでないなら、そのまま返す。
CODEは結局は同じ関数を参照するのだから、デリファレンスの必要はないと思う。
GLOBとかは使わないから良くわかんない。無くてもとりあえず困らない。
あとココによると他にもリファレンスの種類があるけど、
今までPerlを使ってきて見たこと無いので、大丈夫だろう。



__PACKAGE__と比較しているのは、blessされた変数をrefすると、パッケージ名を返すので、
今回使用しているパッケージのコンストラクタはhashをblessするので、
とりあえずhashと同じ扱いで展開しています。