読者です 読者をやめる 読者になる 読者になる

Perlで破壊的だったり破壊的でなかったりする関数を書く

引数を上書きしてしまう関数を破壊的関数と言います。
Perlだとchompが代表的ですね。

$str = "abc\n";
chomp $str;  ### $strの中身が書き換わる(改行が削除される)
print $str;

ucやsortは対照的に、引数には変更を加えず変更後の値を返り値で返します。

$str = "abc";
$uc_str = uc $str;
print "$str\n";    ### abc(そのまま)
print "$uc_str\n"; ### ABC


@old = (3,1,2);
@new = sort @old;
print @old, "\n";  ### 312(そのまま)
print @new, "\n";  ### 123

でも「chompした文字列を出力したいけど元の変数は変えたくない」というときはどうしましょう?
(そんなときないよ!ってツッコミは禁止!)
テンポラリ引数を作ります・・・?

$chomped_str = $str;
chomp $chomped_str;
print $chomped_str;

なんかこう書けたら楽だなーと思いますよね?

print chomp $str;


逆にsortで元の配列を上書きしたいときって

@array = sort @array;

もうこれで上書きしてくれていいのに!と思うこともありますよね?

sort @array;


というわけで同じ機能を持った破壊的な関数と破壊的でない関数、使い分けられると便利なことがあります。
Perlでそのような関数を実装するにはどうすればいいでしょうか。
やはりコンテキストを用いるのがPerlらしいと思います。別関数を定義するのではなく、コンテキストで処理を切り替える。
つまり返り値が求められていないvoidコンテキストのときは引数を上書きし、それ以外のコンテキストでは返り値として返す。

実際に書いてみよう!!!

コンテキストの判別にはwantarrayを用います。
あまり知られてないような気がしますが、wantarrayはスカラーコンテキストとアレイコンテキストだけでなくvoidコンテキストも見分けられます。
voidコンテキストでの返り値はundefです。

voidコンテキスト undef
スカラーコンテキスト 0
アレイコンテキスト 1
改良chomp

簡略化のため引数は1つだけと想定してます

sub my_chomp {
  if(not defined wantarray){
    chomp $_[0];
  }else{
    my $tmp = $_[0];
    chomp $tmp;
    return $tmp;
  }
}


my $str = "abc\n";

my $str2 = my_chomp($str);
print $str  ."#";  ### そのまま
print $str2 ."#";  ### 改行削除されてる

my_chomp($str);
print $str ."#";   ### 改行削除されてる
改良uc
sub my_uc {
  if(not defined wantarray){
    $_[0] = uc $_[0];
  }else{
    return uc $_[0];
  }
}

my $str = "abc";

my $str2 = my_uc($str);
print $str  ."\n";  ### abc
print $str2 ."\n";  ### ABC

my_uc($str);
print $str ."\n";   ### ABC
改良sort
sub my_sort {
  if(not defined wantarray){
    my @sorted = sort @_;
    for(my $i=0;$i<@_;$i++){
      $_[$i] = $sorted[$i];
    }
  }else{
    return sort @_;
  }
}

my @a = (3,1,2);

my @a2 = my_sort(@a);
print @a,  "\n";  ### 312
print @a2, "\n";  ### 123

my_sort(@a);
print @a,  "\n";  ### 123


簡単ですね!!!
まぁそもそも破壊的関数(引数以外の変数も考慮して副作用のある関数と言ったほうがいいかも)の存在自体に否定的な意見も多いわけですが・・・そのあたりの議論はまた別のところでー