これはTokyo City University Advent Calendar 2020 21日目の記事だよ
昨日はBob後輩の俺と軍事と飯でした.アメリカ国防総省が核運営にIBM製のメインフレームとフロッピーディスクを使ってた話は有名ですね.「ネットワークと接続してないから安全!」という理論で使い続けてたり,オバマ大統領が見学に行ったときにドン引きしてたりしてたらしいですね.
冗長的なif文
たとえば1~4の数字を受けとってそれぞれで異なる処理をするプログラムを書くとき
if($input == 1){
&func1();
}elsif($input == 2){
&func2();
}elsif($input == 3){
&func3();
}elsif($input == 4){
&func4();
}else{
print "error";
}
といった風に習うんですよね.大学一年で受けるプログラミングの講義とかでもこんな風に書くと思います.一年生はperlで書かない
けどこれだと色々と大変になっちゃいます
何より見にくい
入力の範囲を1~20まで増やしたい場合
if($input == 1){
&func1();
}elsif($input == 2){
&func2();
}elsif($input == 3){
&func3();
}elsif($input == 4){
&func4();
}elsif($input == 5){
&func5();
}elsif($input == 6){
&func6();
}elsif($input == 7){
&func7();
}elsif($input == 8){
&func8();
...
}elsif($input == 20){
&func20();
}else{
print "error";
}
一つのブロックあたり2行かかるので,if文でずらずらと書いているだけで40行文になってしまいます.少ない数字にも見えますが,ターミナルで作業すると画面の半分以上が似た構造のif文が連続している絵になりなかなか見にくい状態になってしまいます.これが1~40まで範囲が増えるだけで画面に表示されるコード全てがif-elseの文章となり,恐しい光景が広がります.
保守性の低さ
3の倍数で新たに処理,しかもそれぞれ異なる処理を追加する場合
if($input == 1){
&func1();
}elsif($input == 2){
&func2();
}elsif($input == 3){
&func3();
&newfunc1();
}elsif($input == 4){
&func4();
}elsif($input == 5){
&func5();
}elsif($input == 6){
&func6();
&newfunc2();
}elsif($input == 7){
&func7();
}elsif($input == 8){
&func8();
...
}elsif($input == 20){
&func20();
}else{
print "error";
}
もうめんどくさいですね.
ハッシュとリファレンスを使ったクールなポリモーフィズム
こんなかんじでダラダラとif-elseを繰り替えすプログラムは辞めてもっとクールなプログラムを書きたい.今回はPerlのハッシュとリファレンスを使いますが,似たような機能はCにもRubyにもあると思います.
ハッシュ
俗に言う連想配列ってやつです.PythonとかRubyには組込みライブラリとして実装されてますね.C++やJavaにもクラスとして定義されていたはずです.Cでやろうとすると地獄を見ると思います.
Perlだと
%hashsample = (
hoge => 'hogehoge',
foo => 'foobar',
fuga => 'fugafuga'
);
みたいに書けます.使うときは
print "$hashsample{'hoge'}"; #hogehogeがprintされる
といったかんじです.このときhoge
やfoo
など格納したデータを呼び出す変数をキー,参照されるデータを値と言います.
リファレンス
他言語で言うところのポインタです.Perlだとちょこっと性能が違うためリファレンスという名前になってます.
Perlが強力な言語たる所以はリファレンスにあります.リファレンスとはPerlにおけるデータ型の一種で「データの格納場所」を保存する型になります.こう聞くとCで言うところのポインタに思えるけど実際にはもう少し抽象的なもので,たとえば1を足して次のアドレスにアクセスするなんてことはできない,けどポインタよりも安全に直接的に操作することができます.
# スカラー変数
$scalar = "hoge";
# スカラー変数からリファレンスへ代入
$scalar_ref = \$scalar;
# 配列やハッシュにも同じ操作ができる
@array = (1,2,3);
#配列をリファレンスへ代入
$array_ref = \@array;
#配列のリファレンスを直接生成
$array = [1,2,3];
#配列のリファレンスから値を参照
$num = $array->[0] #1が代入される
#リファレンスから配列を取り出す(デリファレンス)
@newarray = @{$array_ref};
#ハッシュ
%hash = ( hoge=>'hogehoge',foo=>'foobar');
#ハッシュのリファレンス
$hash_ref = \%hash;
#リファレンスから取り出し
$string = $hash_ref->{'hoge'}; #hogehogeが代入される
ここではサックリとしか説明しませんが,詳しくはperldocとか読んでみてください.
サブルーチンのリファレンス
さてさきほど紹介したリファレンスという概念,変数だけでなく関数にもできちゃいます.
# 引数に1を加算する関数
sub addone{
return $_ + 1;
}
#関数をリファレンスとして変数に代入
my $sub_ref = \&addone();
my $num = 1;
#呼び出し
$num = $sub_ref->($num);
これだけ見ると一体何が嬉しのかよくわからないですね.
ここでさっき紹介したハッシュを使います.関数がデータのように変数に格納できるのであれば,ハッシュの値として関数を格納することができます.
つまり何を言いたいのかというと
sub addone{
return $_ +1;
}
sub addtwo{
return $_ + 1;
}
sub twice{
return $_ * 2;
}
$func_table = {
one => \&addone(),
two => \&addtwo(),
twice => \&twice(),
}
my $num = 1;
$num = $func_table{'one'}->($num); # addone()が呼び出される
$num = $func_table{'two'}->($num); # addtwo()が呼び出される
$num = $func_table{'twice'}->($num); #twice()が呼び出される
このようにハッシュの値を参照する形で関数の呼び出しを行うことができるようになる.
時を戻そう
冒頭に書いたif-elseの連続する文をハッシュとリファレンスを用いた文に書きかえると
$func_table = {
1 => \&func1(),
2 => \&func2(),
3 => \&func3(),
4 => \&func4(),
}
if(exists($func_table->{$input})){
$func_table->{$input}->();
}else{
print "error";
}
if-elseを使った選択処理なのに,分岐自体にはif-elseを使わないで済みます.(existsはハッシュの値が存在するかどうか調べる関数です)
これなら条件を増やしたい場合はfunc_table
に追加していくだけで済み,ソースコードの可読性も上ります.何よりかっこいい
このように,「同じ記述であるのに異なる関数が呼び出される」のをポリモーフィズムと言います
※厳密にはアドホックポリモーフィズムと言います,いわゆる多重定義です
おわり
この記事はTokyo City University Advent Calendar 2020 21日目の記事でした.21日目の記事なのに投稿が22日になってしまし本当に申し訳ないです.
明日は111111111さんのゲーム配信を始めようです(僕です)