対象読者
- プログラムはかじっているものの、Perlという言語をよく知らない。
- 同じ「P」から始まるなら「Python」一択っしょ!Perlって何ですか?**古文書ですか?**やば!とか思っているし、実際に口に出している。
- そんな毒を吐いて、イキり散らしていたらPerlの現場にアサインされてしまった。
- 「リファレンス」「コンテキスト」という言葉の響きだけで頭痛・めまい・嘔吐の症状が認められ、Perlを出来るだけ触らないようにと医者から言われている。
はじめに
Perlはラリー・ウォールによって開発された動的型付け言語です。
「There's More Than One Way To Do It.(TMTOWTDI)」
というスローガンが示す通り
「やり方は幾らでもある」という思想を元にした
設計の柔軟性に富んだ素晴らしい言語です。
C言語やsed、awk、シェルスクリプトなど古き良きレガシーな知見を取り入れつつ
更にはクロスプラットフォームかつ軽快、という挙動を以って
「CGIと言えばPerl」「文字列処理(正規表現)と言えばPerl」
という確固たる地位を確立しながら
Webシステムを始めとしたITアーキテクチャの発展を後押ししつつ
PythonやRuby、並びにJavascriptへ与えた言語的な影響は少なくない
というそんな素晴らしい言語なのです。
「あそこのレンタルサーバはPerlが使えないから別の所にしよう。」
そんな理由がホームページ開設のロケーション判断基準になるほど
かつてのPerlはプログラム言語適用シーンにおける選択肢として
なくてはならないものだったのです。
今はどうでしょう?
「嫌いな言語」ランキング、
「今これを勉強しても稼げない言語1」ランキング、
といった不名誉なワーストクラスタにおける上位ランカーの常連であり、
現実的な生産性・可読性の面からみても
お世辞にも**「よい言語」とは評価されにくい**現状のようです。
ただ、待ってください。
たったの一側面だけを見て
Perlという言語を見切ってしまうのは余りにも早計過ぎやしませんか?
たとえば、プログラム言語の起こりから
現在まで極めつくしている仙人のような先達が言うならともかく
プログラム言語の初学者が軽々にPerlを切り捨てるのは
単純にただただ勿体ないと思う次第であります。
一生懸命、汗水垂らして、何なら血涙も伴いながら深堀りしないとわからないような
プログラム言語のそれぞれの思想、その骨子の一端がPerlには詰まっているのに。
また、Perlが結果として嫌われてしまうシーンの一つとして
仕事でどうしてもPerlを扱わざるを得なくなった事
に起因するケースが挙げられますが
「めんどくさい」「やりたくない」「なんでこのご時世にPerlなんか」
といった負の感情にただ身を委ねるのではなく
どうしたってPerlを触る事が不可避なのであれば
Perlという言語そのものに対して前向きに付き合うのがベターだと考えます。
(そういった姿勢はPerlに限った話ではありませんけども。)
にしたって実際の現場に身を置いた際に
**足の指でタイピングしたのかな?**と思うような既存のPerlプログラムを読んでいると
「あぁ…もうこれ以上Perlと関わりたくねぇなぁ…」
とナチュラルに思う事が多々あるのは確かです。
下手したらその現場には綺麗なソースコードなんて1バイトもないかもしれません。
読むのも骨だし、ましてや直すのなんて一層骨でしょう。
ですが、10年以上、ましてや仕事でPerlを触った事のなかった著者が
実際に現場でPerlを触ってみて至った結論は
「Perl現場はサブルーチン作成までの基礎知識で十分戦える」
でした。
つまり、まとめると本稿では
- Perlはいい言語!CGIはいい文化!
- だからPerlが無慈悲に足蹴にされるのは何となくモヤモヤする!コツ教えたろ!
- でもそこまでPerlに詳しくないし、そもそも深堀りしたくもないし…(震え声)
といった心理的葛藤を著者自身の為に紐解くとともに
「Perlをゼロから追求したい」
「Perlの開発実績を基にして他の言語にもその実地経験を活かしたい」
といった方の為にも、現場でPerlと「とりあえず」うまく付き合えるコツなんぞを
共有させて貰えたらこれ幸いと思い立ち筆を執りました。
以下、本稿は物語形式で進行します。
無駄に長いです。
ご承知おきください。
プロローグ
~~内示当日~~
新人「(よーし。もう長かった新人研修も終わりに近いぞ!)」
新人「(配属はゴリゴリの開発志望で出したし、
ウチの会社はPythonの現場がほとんどと聞いているし、
キャンパスで培った俺のロジカルな地頭を発揮できる時が来たようだな!)」
新人「(Pythonの本は一通り読んだし、
自分である程度ポートフォリオも組み立てられたし、
色々なワークショップも経て、
有識者のフィードバックも得ている。
足りない実地経験は競プロに積極的に手を出して補充もしている!)」
新人「(無論、Pythonの現場にジョインしたい事は会社にも伝えているし、
研修の実績と評価は上々との自負もある!)」
新人「(あとは現場に出て、実践的なPythonの経験値を積むだけだ!)」
部長「あー、新人君」
新人「は、はい!何でしょうか!」
部長「君の、配属先ね…」
新人「はい!」
部長「パ…」
新人「(焦らさないで!早く!早くちょうだい!)」
部長「Perl」
新人「イエス!パイソン!……え?今、なんて?」
部長「Perl」
新人「」
冒険の始まり
~~着任初日~~
???「…新人君…新人君!?」
新人「(zzz)ハッ…はい!」
???「まったく…最近の若い子と来たら…
寝る暇があったら1バイトでも多くコードを書いて欲しいものですよ。」
新人「…すいません。」
???「このPerlを愛し、Perlに愛された男、
**陰照 眼鏡(いんてり めがね)**の感性からすると考えられませんよ。
(メガネクイ)(メガネの縁キラリン)」
新人「(陰照先輩の説明は密度が濃すぎて付いていけないんだよな…)」
新人「(Perlだからモチベーションも上がらないし…)」
陰照「では、おさらいです。」
Perlの変数型
Perlは大きく「スカラ」と「スカラでない」型に分かれます。
「スカラ」とは「文字列」「数値」など一般的な変数値に加えて
「リファレンス(後述)」を示します。
「スカラでない」とは「配列」や「ハッシュ」など、特殊な変数の型を指します。
変数を宣言するには**「Sigil(シジル、シギル)」**と呼ばれるプレフィックス記号を
変数の頭に置く必要があります。
Sigil
$ : スカラ(文字列・数値・リファレンス)
@ : 配列
% : ハッシュ
また、変数の宣言時にはmyをつけましょう。
# 数値
my $num = 0;
# 文字列
my $str = 'aiueo';
# 配列
my @arr = (1, 2, 3);
print $arr[1]; #「2」を出力
# ハッシュ
my %hash = ('a'=>4, 'b'=>5);
print $hash{'a'} #「4」を出力
print $hash{a} #添え字の囲み文字(シングルクォーテーション)は省略可能
リファレンスとデリファレンス
リファレンスとは何でしょうか?
ポインタのようなものです。(メガネクイ)
その変数が格納されているメモリアドレスそのものを指します。
変数のリファレンスを得るには
変数の頭に「\(バックスラッシュ)」を付与します。
# 配列のリファレンスを得る方法
# 文字列が格納された配列
my @arr = ('a', 'b', 'c');
# 「\(バックスラッシュ)」をつけてリファレンスを得る。
my $ref_arr_1 = \@arr;
リファレンスはただのアドレスですから、
リファレンス経由で変数の中身にアクセスする際には
**アロー演算子(->)**を使用する必要があります。
# 配列
my @arr = ('a', 'b', 'c');
# 配列にアクセス
print $arr[0]; #'a'を出力
print $arr[1]; #'b'を出力
print $arr[2]; #'c'を出力
# 配列のリファレンスを宣言
my $ref_arr = \@arr;
# アロー演算子で配列にアクセス
print $ref_arr->[0]; #'a'を出力
print $ref_arr->[1]; #'b'を出力
print $ref_arr->[2]; #'c'を出力
このように、リファレンスを経由して元のデータにアクセスする事を
デリファレンスと呼びます。
デリファレンスの方式としては
この他にデリファレンス修飾子を使うやり方があります。
デリファレンス修飾子
${} : スカラ
@{} : 配列
%{} : ハッシュ
# 配列
my @arr = (1, 2, 3);
# 配列のリファレンス
my $ref_arr = \@arr;
# 配列のデリファレンス
my @deref_arr = @{$ref_arr};
# 配列のデリファレンス(構文糖)
# 中身が単純値の場合、{}を省略可能
my @deref_arr2 = @$ref_arr;
# 配列のデリファレンス(構文糖)
# 配列のリファレンスから直接格納値へ一直線にアクセス。
# これは${$ref_arr}[0]という意味である。
my $val = $$ref_arr[0];
またリファレンスを利用して無名配列、無名ハッシュを作成する事もできます。
名称を伴う変数を基にしてリファレンスを生成するのではなく、
いきなりリファレンスから宣言するので**「無名」**という事ですね。
# 無名配列の宣言。()ではなく[]を使う。
my $ref_arr = [1, 2, 3];
print $ref_arr->[0]; #「1」を出力
# 無名配列のデリファレンス。
my @deref_arr = @{$ref_arr};
print $deref_arr[0]; #「1」を出力
# 無名ハッシュの宣言。()ではなく{}を使う。
my $ref_hash = {a=>4, b=>5};
print $ref_hash->{a}; #「4」を出力
# 無名ハッシュのデリファレンス。
my %deref_hash = %{$ref_hash};
print $deref_hash{a}; #「4」を出力
コンテキスト
~~復習中~~
カタカタ カタカタ(コーディング中)
新人「(うぅ…記号が多い…頭が痛い…吐き気がする…)」
新人「あれ?」
陰照「どうしました?新人君」
新人「今、教わった内容をprintで確認していたんですが、見え方がおかしいんです。」
陰照「どれどれ」
my @arr = ('a', 'b', 'c');
# 1
print @arr;
print "\n";
# 2
print @arr."\n";
新人「1のprintでは「abc」と出力されるのに
2のprintでは「3」と出力されました。
どういう事ですか?」
陰照「フフフ…それがコンテキストです。」
コンテキストとは?
「文脈」という意味です。
Perlでは前後の記述の「文脈」を解釈し、
その文脈評価の一環として変数を扱います。
では「文脈」とは何でしょうか?
次の日本語の「文脈」を考えてみましょう。
『彼は外出していなかった』
前後の文章や背景がわからない状態だと
この日本語は2通りに解釈できます。
A. 彼は既に出かけており、家にいない状態である。
B. (例:出かけているであろうという予想に反して)彼は家にいる状態である。
もしくは、過去の時点で「家にいる状態」であった。
このように「いなかった」という文節に対して
どういう意味で捉えるか、解釈するか、
この評価軸をコンテキストと言います。
コンテキストの種類
Perlのコンテキストには
大きく「スカラコンテキスト」と「リストコンテキスト」の
2種類があります。
以下にそれぞれの代表的なコンテキストを挙げます。
<スカラコンテキスト>
- スカラ変数への代入
my $hoge = 'aaa';
- 比較演算子の左項と右項
$hoge < $fuga
- ifの条件部
if(EXPR)
<リストコンテキスト>
- 配列への代入
my @hoge = (1, 2, 3);
- リスト表記の内部
(1, 2, 3)
- サブルーチンの引数
subroutine(EXPR)
ともあれ、先ほどの答えを申し上げますと
「配列はリストコンテキストで評価される場合は配列そのものとして解釈されるが
配列がスカラコンテキストで評価される場合は配列の要素数として解釈される」
という事です。
# 配列
my @arr = ('a', 'b', 'c');
# 1 リストコンテキスト
# リスト(配列)を出力する。
print @arr;
print "\n";
# 2 スカラコンテキスト
# 文字列との結合が求められている為
# Perlインタプリタはこれをスカラコンテキストで評価し
# 配列の要素数が返却される。
print @arr."\n";
ちなみに、配列の要素数を得たい場合は
「$#」での配列表現が配列末尾のインデックスを示す事を利用し
表現する事も可能です。
合わせて覚えておくとよいでしょう。
# 配列
my @arr = ('a', 'b', 'c');
# 配列の要素数(代入によるスカラコンテキストでの評価により要素数を得る)
my $cnt_arr = @arr;
print $cnt_arr; #「3」を出力
# 配列末尾のインデックス
print $#arr; #「2」を出力
# 配列の要素数
print $#arr + 1; #「3」を出力
陰照「こんな感じです。わかりましたか?」
新人「はい…(何となくはわかったけど、全然付いていけていない…)」
陰照「新人君は他言語での開発経験があると聞いていますので
早速、実地でPerlをゴリゴリ開発して頂きます。」
新人「いきなり!?」
陰照「えぇ。時間も人手もありませんので。」
新人「(やれる気がまったくしない…)」
陰照「あと、教えた内容についてはほんの触りなので、時間を見つけて
自分でPerlの学習をしておいてください。
そうですね。最低限、ここらへんは一通り読んでおいて欲しいですね。」
ドン
初めてのPerl 第7版
続・初めてのPerl 改訂第2版
プログラミングPerl 第3版 VOLUME 1
プログラミングPerl 第3版 VOLUME 2
新人「全部で2000ページ以上あるんですが…」
陰照「それが何か?」
新人「」
陰照「Perl道はそんな甘くないですからね…(ニチャア)」
新人「ヒィッ!」
冒険の道しるべ
~~着任一か月後~~
カタカタカタカタカタカタカタカタ…
新人「(疲れた…)」
新人「(ろくな仕様書も設計書もないし…)」
新人「(既存コードは何書いているかよくわからないし…)」
新人「(コードによっても書き味はバラバラだし…)」
新人「(やっている事と言えばコピペ&改変がせいぜい…)」
新人「(目の前の作業に追われて参考書を読む気力すら沸かない…)」
新人「(Perlだしこんなもんでいいのかな…)」
新人「(次の現場で頑張ればいいか…)」
新人「それにしても陰照先輩、今日は遅いな。休みかな?」
???「Perlの泣き声が聴こえるな」
新人「!?いつの間に後ろに!?アナタは?」
???「俺の名前は遣付 太郎(やっつけ たろう)。
今日から君の面倒を見ることになった。」
新人「え?陰照先輩は?」
遣付「アイツは死んだ。」
新人「は?」
遣付「Perlと深く関わり過ぎたんだ。闇に飲まれてしまった。」
新人「どういう事ですか?」
遣付「ヒトとしては生きているがな。詳しくは言えない。
鉄格子に囲まれている状態だとだけ言っておこう。」
新人「で、でもどうするんですか。
次のリリース…えっと、バージョン1532ですね。
もう時間もないし、陰照先輩のタスクもあったのに…」
遣付「安心しろ。その為に俺がここに来た。
業務仕様はすべて頭の中に入っている。」
新人「よかった。じゃあPerlもお詳しいんですね。」
遣付「全然。」
新人「え?今なんと?」
遣付「Perlはやった事ない。今さっきPerl入門ゼミをざっと読んできたところだ。」
新人「えぇぇぇぇぇぇ!?」
課題の炙り出し(As-is)
新人「い、いやいや、Perlを知らないで大丈夫なんですか?」
遣付「大丈夫だろう。」
新人「だろうって…」
遣付「よし。まずは現状整理だ。認識が正しいか一応確認させてくれ。」
新人「わかりました…(大変な事になったぞ。本当に大丈夫か?この人。)」
見えている課題
1.Perlプログラムの仕様書・設計書がない。
2.Perlに詳しい人がいない。(いなくなった。)
3.Perlを作らなくてはいけない。
遣付「まず大きい所でこんなもんだな。」
新人「はい。その通りです。」
遣付「…加えて、ズバリこうだな。」
4.既存のPerlコードが読み辛い。しかも書き味がバラバラ。
5.Perlコードとして何をどう書けば正解なのかがわからない。
6.なので終始、「読み辛いけど、動く事は担保されている」であろう
既存Perlコードのコピペに追われている。
7.コピペが主力なので、そもそものアルゴリズムやサブルーチンの
構造を組み直す事も考えたくない。
8.結果として一つのサブルーチンがものすごく長くなる。
9.自分で書いたコードですら読み直したくない。
10.総じてPerlへのモチベーションが低い。
新人「(ギクッ!!)…恐れ入ります。その通りです。」
遣付「君の書いたコードも含めて既存コードは一通り、確認させて貰ったよ。」
新人「(うぅ…怒られそう。)」
遣付「でも、しょうがない。」
新人「え?」
遣付「上に挙げた1~3まではどうしようもない前提だ。
君に限らず誰かが頑張ったところですぐにどうにかなる問題でもない。
出来上がったスパゲッティはスパゲッティでしかない。
それに塩をかけたり、水で洗ったり、こねたりした所で
スパゲッティである事は変えられんさ。
作り直しでもしない限りね。」
新人「はぁ…」
遣付「もし、万が一、ユーザにとって唸るほどの金が突然産まれて
『よし、このPerlコードを全部作り直そう』
となったとする。まかり間違って。億兆分の一の奇跡が起こって。
起こらない。
現実的には絶対にそんな奇跡は起こらないんだけど
仮の話だ、まずそう仮定させてくれ。」
新人「はい。」
遣付「そうなった時、もし君ならPerlで作るか?」
新人「作らない…と思います。」
遣付「そうだろう?
だから1~3までは過去の事象。変えられないただの出来事。
自分が与り知らない過去の経緯にWhyを求めたり、
現時点で自分がPerlを触ることにWhyを求めても仕方ないさ。
不毛なだけだよ。
ここまではいいかな?」
新人「はい。大丈夫です。」
遣付「よし。それじゃあ、次にどうあるべきかを考えてみよう。」
求められるシステムとあるべき自分(To-be)
遣付「それにしても、バージョン1532か…
よくもこんなにPerlのシステムを長々と運用してきたものだな。」
新人「えぇ。とんでもないです。」
遣付「これからも当分はこの変態的なバージョニングが続くんだろうな。」
新人「…」
遣付「だからここのシステムのあるべき姿っていうのは単純だ。
**動きゃいい。**それだけだ。
どんなにつまんねぇ要求仕様に対しても粛々と改造を施され
必要なシステムとして運用される限り稼働し続けて
寿命を全うすればいいさ。」
新人「そう…ですね。」
遣付「君はどうだ?」
新人「え?」
遣付「君のあるべき姿だ。」
新人「自分のあるべき姿…」
遣付「コピペマシーンになる事ではないだろう?」
新人「自分は…」
新人「自分は…できれば他の言語で開発がしたいです。」
遣付「ふむ。」
新人「その為にも何か潰しが利く形で知見を広げられればと考えています。
汎用的と言いますか…。
色々な武器を持っていて、更にそれらを自在に使いこなせる状態…。
うまく言えないですが、それがあるべき姿かと思います。」
遣付「なるほど。
では、Perl開発はその潰しが効く形で知見を広げるには向いていないと?」
新人「正直…はい。そうです。向いていないと思います。」
遣付「いいだろう。」
本当にするべきこと(To-do)
遣付「君は根本的に勘違いをしている。」
新人「Perlに対してですか?」
遣付「いや、もっと大きな観点だな。言語に対してというべきか。」
新人「どういう事でしょうか。」
遣付「アルゴリズムという言葉は知っているな。」
新人「はい。これですね。」
アルゴリズム(英: algorithm [ˈælgəˌrɪðəm])とは、数学、コンピューティング、言語学、あるいは関連する分野において、問題を解くための手順を定式化した形で表現したものを言う。算法と訳されることもある。
「問題」はその「解」を持っているが、アルゴリズムは正しくその解を得るための具体的手順および根拠を与える。さらに多くの場合において効率性が重要となる。
コンピュータにアルゴリズムをソフトウェア的に実装するものがコンピュータプログラムである。人間より速く大量に計算ができるのがコンピュータの強みであるが、その計算が正しく効率的であるためには、正しく効率的なアルゴリズムに基づいたものでなければならない。
(出典:Wikipedia)
遣付「そう。そしてアルゴリズムを具現化したものがプログラムとも言える。」
新人「はい。それが何か…?」
遣付「これを逆にする事で大きな齟齬が生まれる。
プログラムを具現化する為のものがアルゴリズムであると。」
新人「???同じじゃないんですか?」
遣付「プログラムとアルゴリズムだけのスコープであればそれでいいさ。
でもWikipediaにもある通り、アルゴリズムは広義な言葉だろう?
アルゴリズムがプログラムの為だけにあるとは限らないという事だよ。」
新人「うーん…なるほど。」
遣付「では聞こう。Perlはアルゴリズムに対してどういう姿勢を取っている?」
新人「**There's More Than One Way To Do It.(TMTOWTDI)**ですか?」
遣付「そう。問題はそこなんだ。」
「TMTOWTDI(やり方は幾らでもある)」に対する誤謬
遣付「There's More Than One Way To Do It.(TMTOWTDI)は
決して誤ったスローガンではない。
むしろPerlのAPIは設計の自由度を広げる事に努めているように思う。」
新人「では何が問題だと?」
遣付「よし。ではあらためて、課題の4番と5番を振り返ってみよう。
ちなみに6番以降は4番5番に大きく起因する内容なのでもう忘れて結構だ。」
4.既存のPerlコードが読み辛い。しかも書き味がバラバラ。
5.Perlコードとして何をどう書けば正解なのかがわからない。
遣付「どうだ?
こいつらはTMTOWTDIの賜物か?それとも弊害か?」
新人「弊害…だと思います。」
遣付「何故こんな事が起きたんだろう?」
新人「やり方があり過ぎるからですかね。」
遣付「半分正解だ。
やり方は幾らあってもいい。
でも好き勝手に書いてはダメという事さ。」
新人「うーん、耳が痛いですね。」
遣付「どれだけ設計の間口が開かれているのか、
つまり設計の自由度は言語のポテンシャルによるのかもしれない。
そういった意味でPerlは十分な設計の自由度を有している。
ただ、どう設計するのかはエンジニアの責任でしかないという事だね。」
新人「なるほど。
確かに自分で書いたコードの書き味がバラバラな事も往々にしてあります。」
遣付「特に、複数人での開発プロジェクトになると致命的になる。
今がまさにそうだろう?
色んな人間が好き勝手にコードを書いた顛末がこれだ。」
新人「おっしゃる通りだと思います。」
遣付「じゃあ、逆により対極にある観点を考えてみるとしよう。
君の好きなThe Zen of Pythonを思い出してみるといい。
本質的なニュアンスはちょっと違うかもだが、言葉だけに注目してくれ。」
There should be one-- and preferably only one --obvious way to do it.
一つの(願わくは一つだけの)明確な手段があるはずだ。
新人「なるほど。TMTOWTDIとは真逆な印象を受けます。」
遣付「極端に言うなら**『やり方は一つしかない(一つであるべき)』**
という考え方だね。
これだとどうだ?
実際のPerl開発に耐えられるだろうか?」
新人「何となく厳しい気がしますね。
何が何でもやり方が一つだけしかないのであれば、
コードはその分見やすくなるかもしれませんが、生産性も落ちる気がします。」
遣付「その通り。
コーディングの自由度を全部殺すと、逆に生産性が落ちてしまう。
でも設計の自由度は担保したい。
先にも言った通り、
『幾らでもあるやり方の中から優れたやり方を選択するのはエンジニアの責任』
だからね。」
新人「うーん…難しい。」
遣付「だが、これらを解決するたった一つの優れたやり方が存在する。
設計の自由度も生産性もそこそこ担保するやり方が。」
新人「えっ。それは何ですか?」
遣付「それがアルゴリズムの為のアルゴリズム。標準化だ。」
標準化とは
新人「標準化?」
遣付「そう。言葉の定義を確認してみよう。」
標準化(ひょうじゅんか、英語:standardization(スタンダーダイゼーション))という用語は、文脈によって様々な意味を持つ。「標準(standard)」という用語には、相互運用のための広く合意されたガイドラインという意味が含まれ、「標準化」はそのような標準を確立する過程を指すのが一般的である。
(出典:Wikipedia)
新人「これも随分と広義な言葉のようですね。
Perl開発においてはどういう意味で捉えればよいでしょうか?」
遣付「言葉通りと言えば言葉通りだけどな。
開発を進めるに当たって
『こんな風にコーディングしていきましょう』というやり方を
あらかじめメンバー間の合意の元で決める。
それをルールとして『標準化仕様』や『コーディング規約』といった形で
共有・遵守すればよいだけだよ。」
新人「『好き勝手にコーディングできなくなる』のはよいと思います。
ですが、やり方を狭めるという事による弊害はないのでしょうか?」
遣付「いい質問だ。
標準化というのはルールを決めるという事自体も大事なんだけど
それ以上に過程をシンプルにするというのも大事な目的の一つなんだ。
厳密に、厳格に、限界までルール付けするのであれば
それはPerlの言語仕様と変わらないだろう?」
新人「それは確かに。」
遣付「だから、ある程度のパターン化・カテゴリ化を施す事で
コーディングの方式をより少ない選択肢の中から選べるようにする。
これでどうやって作ったらよいかわからないという暗中模索の状態に陥る事も
ある程度、回避できるわけだ。」
新人「なるほど。確かに。
お恥ずかしい話、コピペする時は
コードのauthorを見たり、コードの読み易さを見たりして
コピペ元として適切かどうか判断していたりもしましたが
何となく『標準化』の方がよほど効率が良さそうですね。」
遣付「そうだ。
好き勝手に書かれたコードだと、
その人がどんな理由で、どんな目的で、はたまたどんな気分で
結果としてどのようなアルゴリズムに至った末に書いたのかが伺い知れない。
下手したら、機嫌によって書き味が変わる人かもしれないしな(笑)」
新人「そんなコードもなくはないです。(笑)」
遣付「さて、まとめだ。
前置きが長くなったが、要は標準化こそTodoという事だ。
君の知見を広げてみせる為にも
具体的な標準化の手段の話に移ろうと思うが
ここまでは大丈夫かな?」
新人「えっと…そうですね。質問が3つあります。」
遣付「よろしい。聞かせてくれ。」
アルゴリズムの為のアルゴリズム
新人「一番最初に言っていた
**『アルゴリズムの為のアルゴリズム』**とは何でしょうか?」
遣付「アルゴリズムというのはWikipediaにも書いてある通り
言ってみれば手順、手続きの事だ。
そしてこうも書いてあるな。
**『多くの場合において効率性が重要となる』**と。」
新人「はい。」
遣付「そしてアルゴリズムがプログラムの為だけにあるとは限らないとも言った。」
新人「えぇ。そうでした。」
遣付「システムを構築する時にはプログラムが必要となる。
そしてそのプログラムを作るときにはアルゴリズムが不可欠だ。
ただシステムは作って終わりではない。」
新人「あ。プログラムを書いた後のアルゴリズムも大切という事でしょうか。」
遣付「その通りだ。
システムを保守・運用する際にもアルゴリズムという概念は存在する。
どう効率的にシステムを保守し運用していくかという考え方だ。」
新人「なるほどー。」
遣付「そして保守という活動の中には当然のように、
プログラム改修も含まれているよね?」
新人「はい。」
遣付「という事は、効率よくプログラムを保守する為のアルゴリズムという
考え方も大事になってくるわけだ。
これはコードを動かすためのアルゴリズムではなく
ややこしい言い方をするのであれば
『コードを動かすためのアルゴリズム』
を保守しやすいようにどう建て付けるかのアルゴリズム
という事になる。」
新人「はい。何となくわかります。
要はプログラム改修しやすい方がより良いという事ですね。」
遣付「そういうことだ。」
新人「理解しました。」
標準化の適用範囲
新人「では2つ目を。
今現在、Perl開発は私を含めて2人で行っています。」
遣付「そうだな。」
新人「少人数でもやる必要はあるのでしょうか?
2人くらいであれば都度確認するくらいでもよいのでは…」
遣付「やるべきだ。
むしろ1人でもやるべきだ。」
新人「1人でも!?」
遣付「あぁ。君はさっき自分で言っていたな。
**『自分で書いたコードの書き味がバラバラ』**になる事があると。」
新人「あ」
遣付「そこに『コーディングする際の迷い』なり
『書き味の粒度のバラつき』があって、
保守しにくくなる可能性があるのであれば
それは保守というスキームにとって
効率的なアルゴリズムではないんだ。」
新人「なるほど…
あ、でもどのソースに対して標準化を徹底すればよいのでしょうか?
過去に遡ってのソースコード修正は現実的でないと伺いましたが…」
遣付「今からでいい。」
新人「これから作るものにだけという事でしょうか。」
遣付「そうだ。
標準化仕様に『●年●月●日から施行』とでも書いておけばよい。」
標準化の優先度
新人「なるほど。では3つ目です。
これが一番聞きたかった事なのですが…
標準化が何となく大事、という事は
ニュアンスだけかも知れませんが、理解できたかと思います。
今やっているやり方よりはマシとも。
ただ、ここまで開発が切羽詰まっている中で
どれだけ優先度を上げて標準化の策定に臨むべきでしょうか?」
遣付「そうだな。時間は有限だもんな。」
新人「はい…」
遣付「答えは最優先だ。」
新人「えぇぇ!?」
遣付「何か問題でも?」
新人「先ほど『標準化仕様』や『コーディング規約』が形として必要って…
そんな時間ありませんよ…」
遣付「おいおい。勘違いしてもらっては困る。
何も、恭(うやうや)しい厳(おごそ)かな仕様書(笑)
を作れって言っているわけではないんだ。」
新人「へ?」
遣付「ぶっちゃけ、メモ帳でいい。
そしてさっきも言ったな、シンプルでいい。
ルールが本質的に共有できるのであれば体裁は要らん。
体裁を気にする仕様書なんざクソだ。」
新人「時間をかけなくてよいって事ですか?」
遣付「そうだ。」
新人「でも、全てのルールをリストアップするのは無理じゃないですか?
適用範囲が『これから作るもの』にだけって言っても…」
遣付「うん。全てのルールを最初にリストアップする必要はない。
全てリストアップできるのであればそれはそれで望ましいが
あまり時間をかけられないのであれば、
適宜、追加なり変更なりしていけばいい。」
新人「それだと書き味がどんどんズレていきませんか?」
遣付「違うんだ。
これから作るソースも含めて
全ての書き味を揃えて進んでいく事が目的ではないよ。
書き味が、同じ形に収束していく事に意味があるんだ。」
新人「…なるほど!」
遣付「プログラムに必要なアルゴリズム。
標準化に必要なアルゴリズム。
これら、アルゴリズムの効率性は常に100%であるとは限らない。
だが、より100%への改善を目指し、高い効率性を保守していく。
これはプログラムも標準化も変わらないよ。」
新人「やれる気がしてきました!」
冒険の心得
遣付「よし。それでは具体的な標準化の内容について触れていこう。」
新人「お願いします。」
遣付「その前に…君はサブルーチンを自前で作った事はあるか?」
新人「実は…数えるくらいしかなくて。
しかも簡単なモノしか書いたことはないです。」
遣付「なるほどな。
では記念すべき最初の標準化はこう銘打とうじゃないか。」
サブルーチン作成の十三か条
新人「十三か条…!」
遣付「安心してくれ。一つ一つはとてもシンプルな内容だ。」
新人「ホッ…ちなみに、策定するのはサブルーチンに関連する事柄だけですか?」
遣付「初版だし、最低限の基本文法についても入れておくよ。
あとは適宜、追加なり変更なり、章立てを組み替えたりしていこう。」
新人「承知しました!
って、でも
何故、あえてサブルーチンを標準化のスコープとしているんですか?」
遣付「これはあくまで個人的な考えではあるけど
サブルーチンを自前で作る事がPerlを理解する為の最善の取っ掛かり
であるように思う。
リファレンスやコンテキストといった
基本的な概念に対する最低限の理解が必要になってくるし
標準化を自分で建て付ける練習にもなるしね。」
新人「はぁ…なるほど…」
遣付「それに、サブルーチンを新規に作る事を嫌ったせいで
鬼のように長くなってしまったサブルーチンを
これ以上、読み直したくはないだろう?」
新人「それは…もう!ホントに!」
遣付「ハハハ…それじゃ早速やっていこう。
繰り返しになるけど、以降で説明する標準化の内容はほんの一例だ。
継続的に標準化の内容を充実させるのは自分自身
という意識を忘れないでくれ。」
第一条:まず「記号」に悩んだらPerldocを見よう。
新人「これは…何ですか?」
遣付「標準化、というよりは初心者向けの心得みたいなものだね。
Perlのソースを読んでて記号が多いと思った事はないかな?」
新人「むしろ記号しかないと最初は思っていました…」
遣付「だよね。
Perlはとても記号が多い言語だ。
表現方法が豊富と言えば聞こえはよいが
反面、混乱を招く要素になっているとも言える。」
新人「確かに。」
遣付「ただこればっかりは覚えてもらうしかない。
コーディングしながら感覚で覚えた方が早いと思う。」
新人「でも全部覚えるのは大変じゃないですか?」
遣付「みっちり隅から隅まで覚える必要はないさ。
言ったろう?感覚で何となくでいいのさ。」
新人「感覚…」
遣付「そう。
例えば、$はスカラ、%はハッシュ、/がたくさん出てきたら正規表現、
それくらいの識字感で最初は十分だ。」
新人「なるほど。」
遣付「そこでPerldocだ。
PerldocはPerl公式のWeb公開されている言語仕様書だね。
関数のAPI仕様だけでなく、Perlの基本ポリシーや文法、データ型、
正規表現、演算子に至るまであらゆる仕様がドキュメント化されている。」
新人「すごいですね…でも英語か…」
遣付「英語が読めるのであれば本家の英語版を読むのがベストだ。
ただ英語が読めなくても大丈夫。
一般社団法人 Japan Perl Association(JPA)の監修の元で
日本語化されたサイトもちゃんとあるんだ。」
新人「あ、これなら安心です。」
遣付「ただ、バージョンは気を付けるようにな。
本家Perldocのバージョンは2019年8月時点で5.30.0が最新だが
日本語化のバージョンは5.26.1が最新だ。」
新人「日本語化の方が少し古いんですね。」
遣付「そう。だから、最新のPerlを使う際には英語版を見た方がよいね。
幸い、この現場は5.10.1という10年前の骨董品を採用しているから
どっちでもいいけど。」
新人「ハハハ…」
遣付「なので、以降の説明では日本語版のサイトを引用していくよ。」
新人「わかりました。
…あれ?この目次にある
perlintroとかperlsynとかのカテゴリは何者ですか?」
遣付「それはただのエイリアスだね。
Perl概要はperlintro、Perl文法はperlsyn、みたいに
カテゴリを示す略称の文字列がPerlドキュメント内で定められているようだよ。」
新人「特に意味はないという事ですか?」
遣付「意味をそこまで気にしなくてもよいけど、
覚えておくと、ググる時とかに手っ取り早いかもね。
あ、文法調べたいから「perlsyn」って検索するか、みたいな。」
新人「なるほどー。
でも、このPerldocもすごい内容量ですね。
参考書くらいのボリュームはあるんじゃないですか?」
遣付「そうだね。
だから記号という観点で参照するのであれば
初手は**演算子(perlop)**から見ていくのがよいと思う。」
新人「演算子だけでも、ものすごい量だ…」
遣付「そう。
ただ記号の意味を見たことあるのとないのとでは、
既存ソースコードを読むスピードが変わってくるので
最低限、演算子は一通りざっと抑えるくらいはしておいた方がよい。
perlopが読み辛い、概要だけ知りたい、という事であれば
外部サイトで概要を掴むというのも最初はアリだろう。」
新人「むむむ…なるほど。」
遣付「記号の早見という意味では公式でもチートシートがあるにはあるんだけど
見やすいとは言えないね。
だから、$
や&
や%
といったよく使う記号について
自分でチートシートを作って目に付く場所に置いておく
というのも効果的だ。
でも、最初は公式ドキュメントにアクセスした上で
perlopなりperlsynなり、
特定のカテゴリを参照する癖をつける事をお勧めしておくよ。」
新人「わかりました。」
遣付「ついでに、最初は見る機会が多いであろうPerldocを厳選しておいた。
合わせて参考にしてみてくれ。」
演算子:perlop(5.26.1)
文法:perlsyn(5.26.1)
組み込み関数:perlfunc(5.26.1)
特殊変数:perlvar(5.26.1)
正規表現:perlre(5.14.1が最新)
診断メッセージ:perldiag(5.22.1が最新) ※いわゆる、エラーメッセージ
新人「とりあえず、初手は演算子をベースに記号を知っておく…と。
承知しました!」
第一条 一項:記号で困ったらとりあえずPerldocを見る。
遣付「あ、厳密に記号ではないが最後にもう一つ。
以降のソースコードでは頭にuse strict;
とuse warnings;
が
書かれているものだと思ってくれ。」
use strict;
use warnings;
新人「お約束事だと思って何となく毎度のように書いてはいるんですが
これらは何ですか?」
遣付「strict
やwarnings
はプラグマと呼ばれる
Perlコンパイラの挙動を変えるモジュールだ。
モジュールというのはよく使われる機能を集めたライブラリのようなもの
だと思ってくれればいい。」
CPAN モジュール - strict
CPAN モジュール - warnings
新人「あれ?でもPerlはインタプリタ型言語ではないのですか?
コンパイラって…」
遣付「そう。Perlはコンパイラとインタプリタの機能を両方持っているんだ。」
Perl は常にコンパイラを持っています: ソースコードは内部コード (構文木) に 変換され、実行する前に最適化されます。
perlcompile - Perl コンパイラ・トランスレータの解説
新人「え?そうなんですか!」
遣付「あぁ。
インタプリタ言語のように動きはするんだけどね。
実際にはPerlスクリプトが実行される前にコンパイルが行われている。」
新人「なるほどー。」
遣付「詳細な説明は省くが、コンパイルが通るという事は
Perlの構文的に問題がない、つまり構文解析が正しく行われ
プログラムが正常に動く状態にある、という事だ。
Perlのプラグマはその構文解析の判定基準を変える。」
新人「ふむふむ。」
遣付「具体的にstrict
プラグマは
変数宣言を強要したり、クォートされていない文字列を禁止したりと
その名(strict)の通り、文法のチェックを厳しくする事ができる、
warnings
プラグマは
例えば、未定義値(undef)を参照しようとしたりした時に
警告を出力する事ができるようになる
というイメージだ。」
新人「書き味というよりは中の動作を揃えるといった感じですね。」
遣付「その通り。
strict
とwarnings
を使うのは
今の認識の通りお約束事だと思って差し支えないと思う。
一般的にも周知の事柄だしね。」
第一条 二項:スクリプトを書く際はstrict
プラグマとwarnings
プラグマを必ず使用する。
use strict;
use warnings;
新人「わざわざuse
文まで書いたのには意味がありますか?」
遣付「例えばだけど、PerlのIDEとしてPycharmやIntelliJを導入したとする。
更に、JetBrainsのPerlプラグインCamelcadeを使ったりすると
デフォルトの設定だと、新規にPerlファイルを作成した時に
use strict;
が自動挿入されるのはいいんだけど
warnings
プラグマはuse warnings FATAL => 'all';
のように
オプションが付いてきてしまう。
これもIDE環境によってはプラグマの宣言が自動生成されたりされなかったりするので
明文化して忘れないようにしておこうというのが目的だね。」
新人「なるほどなるほど。」
遣付「あとはuse文でバージョンを指定したりもできるけど
5.12以降のバージョンをuseすると暗黙的にstrict
プラグマが有効になる
なんてのもある。」
バージョン番号 5.11.0 以上を指定した use VERSION 文法を使うと、 (その他の機能が有効化されるのに追加して) ちょうど use strict と同様、レキシカルに strict が有効になります。 以下の表記は:
use 5.12.0;
以下を意味します:
use strict;
use feature ':5.12';
perl5120delta - perl v5.12.0 での変更点 - 暗黙の strict
遣付「use
文やプラグマ
に関しては奥が深いのでここでは深追いしないが
とりあえずstrict
プラグマとwarnings
プラグマが有効になっていればいい
と思ってくれればいい。」
新人「承知しました!」
第二条:演算子にまつわる書き方を統一しよう。
遣付「では早速、演算子の書き方を揃えていこう。
次のコードを見て欲しい。」
my $flg = 0;
if(! $flg){
print "false";
}
新人「えっと、$flg
が偽なのでfalseが出力されますね。
これが何か?」
遣付「では、これだとどうか?」
my $flg = 0;
if(!$flg){
print "false";
}
新人「同じですが、論理否定の単項演算子!
の後ろにスペースがないですね。」
遣付「その通り。
後ろにスペースがあろうがなかろうが
論理否定演算子として作用する。
どちらでも書けるのであればできる限りどちらかで書こう。
細かい事だがこれも標準化の対象だ。」
新人「なるほど。
では、どちらがよいでしょうか?」
遣付「論理否定を示す単項演算子!
は他の言語、例えばJavaにも存在する。
元々Javaをやっていた人間であれば、
後ろにスペースがない方が馴染みのある分、
読み易いし書きやすいんじゃないかな。
ただ、Perlでは組み込み関数の引数を指定する際も
カッコではなく、スペースで評価させる慣例があるのも事実だ。
# print関数の一例。
# カッコ付でも動くけど…
print('aaa');
# こちらの方が自然だと思う。
print 'aaa';
なので個人的にはスペースで区切った方が自然だとは思う。
けど、やっぱり最終的には現場の好みかな。
if(! $flg){
# 中略
結論、この第二条の項となるお約束事はこんな風になる。
第二条 一項:論理否定演算子!
の後にはスペースを一つ置く。
どうかな?」
新人「理解しました。
ただ、ビシッとルールとして書かれると、
何か縛られているようでモヤモヤしますね。」
遣付「書き味がバラバラになるよりはよっぽどマシさ。」
新人「ですね。」
遣付「あとは論理否定つながりで、例えばnot
だな。」
新人「!
より優先順位が低い論理否定の演算子ですね。
使ったことはありませんが…。」
遣付「よし。じゃあ今回は先に条項を書いてみよう。」
第二条 二項:論理否定演算子not
は使わない。
新人「え。使わなくていいんですか?」
遣付「うん。無理に使う必要は全くない。
むしろ、優先度を履き違えてバグを出すくらいならいっそ使わない、
という選択肢の方が適切だと思う。」
新人「どういう事でしょう?」
遣付「では、否定論理積(NAND)を例に簡単に説明しよう。」
否定論理積(NAND)
A | B | A nand B |
---|---|---|
0 | 0 | 1 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
遣付「否定論理積は命題Aと命題Bが両方真の場合のみ偽となる論理式だ。」
また、Perlには**短絡演算(ショートサーキット演算)**という評価方式がある。」
二項演算子の "||" は、短絡の論理和演算を行ないます。 つまり、左被演算子が真であれば、右被演算子は評価さえ行なわれないということです。
perlop - Perl の演算子と優先順位 - C スタイルの論理和
遣付「これを利用することで、論理演算式はとてもシンプルになる。」
A nand B
= not A || not B
新人「なるほど!
命題Aが偽であれば否定論理積は必ず真という事ですね。
あとは命題Aと命題Bの両方が真であるかを見極めればよい、と。」
遣付「その通り。
では、論理演算をnot
を使って実装してみよう。」
print "A:B [A nand B]\n";
print "-------------\n";
for my $meidai_a (0 .. 1) {
for my $meidai_b (0 .. 1) {
my $judge = (not $meidai_a || not $meidai_b) ? '1' : '0';
print "${meidai_a}:${meidai_b} [${judge}]\n";
}
}
新人「何となく良さそうに見えますが…」
遣付「これが実行結果だ。」
A:B [A nand B]
-------------
0:0 [0]
0:1 [1]
1:0 [0]
1:1 [0]
新人「全然違うー!」
遣付「これが演算子の優先順位だ。
not
の優先順位はうんと低い。
上で使われている論理和の演算子||
よりもっとね。
つまりnot
よりも||
が先に評価されてしまっているんだ。
実際にはこう動いている。」
my $judge = (not ($meidai_a || not $meidai_b)) ? '1' : '0';
新人「あ、そうか。||
の左項を$meidai_a
だけと見てしまうのか…」
遣付「その通り。ではあらためて!
で書き直してみよう。」
print "A:B [A nand B]\n";
print "-------------\n";
for my $meidai_a (0 .. 1) {
for my $meidai_b (0 .. 1) {
my $judge = (! $meidai_a || ! $meidai_b) ? '1' : '0';
print "${meidai_a}:${meidai_b} [${judge}]\n";
}
}
A:B [A nand B]
-------------
0:0 [1]
0:1 [1]
1:0 [1]
1:1 [0]
遣付「良さそうだね。」
新人「でもnot
を使わないと実現できないようなロジックはないのでしょうか?」
遣付「ないと思うなぁ。
どの言語でも単項の論理否定演算子!
はとても優先順位が強いだろう?
もしPerlでの特殊な使いどころがあるのだとしても
潰しが効く/効かないという観点においては
わざわざnot
の弱い優先順特性を活かすメリットはあまり感じられないね。」
新人「not
って直感的な単語なのになんか勿体ないですね。」
遣付「そうだね。
強いて使うのであれば単項評価式というのが前提になるけど
!
の代用として使えなくもない。
でもやっぱりそこまでのメリットはないと思うなぁ。」
if(not $flg){
# 中略
新人「納得しました!」
遣付「あとは基本的な所だとハッシュの初期化かな。
コンマ演算子としてどちらを使うかっていう。」
my %hash1 = ('a', 1, 'b', 2, 'c', 3);
my %hash2 = (a=>1, b=>2, c=>3);
新人「あれ。%hash2
ではキーをクォートしていませんけどいいんですか?」
遣付「そうなんだ。
=>
(fat commma)を使う場合は
裸のリテラル(Bareword)が許容される場合があるんだ。
=> 演算子(時々「ファットコンマ」と発音されます)はコンマ演算子の 同義語ですが、もし左側の単語が文字か下線で始まっていて、かつ文字、数字、 下線でのみ構成されている場合、これを文字列として扱うという効果もあります。
perlop - Perl の演算子と優先順位 - コンマ演算子
んで、Perl上**,
と=>
は完全に同義**だ。」
新人「へー。じゃあ、ハッシュではファットコンマの方がよさそうですね。」
遣付「そうだね。
一目で『お?ハッシュか?』ってわかるし
いちいちクォートしなくてよい、というのは楽だよね。」
第二条 三項:ハッシュのキー・バリューはファットコンマ(=>)でつなぐ。(初期化時)
遣付「演算子はキリがないのでこの辺にしておこうか。」
新人「はい。深堀りするとしたらどんな方向が望ましいですか?」
遣付「うん。演算子は優先順位、そして評価順(方向)がとても大切だ。
十三か条の項と用語が被ってしまっているけど
Perlには**項(TERM)**という概念が存在する。
とても大事な考え方なのでこれをベースにするとよいね。」
新人「ふむふむ。」
遣付「あとは面白い演算子も色々あるので
興味があるなら調べてみるといい。
バージョン5.12.1で登場し、5.14.1で名前を変え、
5.16.1でPerldocから忽然と姿を消した ヤダヤダ演算子とか
アルファベットのエックス(x
)で表現される繰り返し演算子とか
デフォルト値を設定可能な論理定義性和(Defined-or)とかね。」
新人「へー。なんか面白そうですね。」
遣付「そうなんだ。
面白いのはたくさんあるんだけど全部を無理やり使おうとするのは
時間的な制約で難しい場合もあるだろう。
それでもメリットが大きいと判断できて、
実用できそうならどんどん標準化していこう。」
新人「はい。わかりました!」
遣付「よし。次にいこう。」
第三条:構文糖(糖衣構文)はほどほどに。使うなら統一しよう。
遣付「構文糖は聞いたことあるかな?」
新人「はい。書き味を省略できるくらいの認識ですけど。」
遣付「十分だ。では、さっきのハッシュのファットコンマを見てみよう。
%hash1
と%hash2
に加えて%hash3
と%hash4
を追加してみた。」
my %hash1 = ('a', 1, 'b', 2, 'c', 3);
my %hash2 = (a=>1, b=>2, c=>3);
my %hash3 = ('a'=>1, 'b'=>2, 'c'=>3);
my %hash4 = ("a"=>1, "b"=>2, "c"=>3);
新人「あれ。キーのクォートは要らないんじゃ…?」
遣付「そうなんだ。冗長だけどクォートを記載することもできる。
この場合、求められる一例としてはこうだね。」
第三条 一項:ハッシュのキーはクォートしない。
新人「そんなケースがあるかはわかりませんが、
クォートしたい場合はどうしましょうね。」
遣付「いい質問だ。
後で文字列についての標準化も策定するので
そこで詳しく見ていこう。
じゃあ、次だ。」
my @arr = ([1, 2, 3], [4, 5, 6], [7, 8, 9]);
my $ref_arr = \@arr;
# リファレンスからアクセス
print $ref_arr->[1]->[2]."\n";
# デリファレンス修飾子からアクセス
print ${$ref_arr}[1]->[2]."\n";
# デリファレンス修飾子のブレースを省略
print $$ref_arr[1]->[2]."\n";
# 別の変数にデリファレンスして代入
my @deref_arr = @{$ref_arr};
print $deref_arr[1]->[2]."\n";
新人「えっと…多次元配列っぽい、配列の配列ですね。」
遣付「そうだ。
これらの表記を更に省略する事ができる。」
矢印は、二つの 添え字 の間にあるのなら、省略できます。
perlreftut - Mark によるリファレンスに関するとても短いチュートリアル - 矢印のルール
遣付「つまり、アロー演算子->
の両側が()
,[]
,{}
の場合は省略が可能という事だ。」
my @arr = ([1, 2, 3], [4, 5, 6], [7, 8, 9]);
my $ref_arr = \@arr;
# リファレンスからアクセス(アロー演算子を一部省略)
print $ref_arr->[1][2]."\n";
# デリファレンス修飾子からアクセス(アロー演算子を省略)
print ${$ref_arr}[1][2]."\n";
# デリファレンス修飾子のブレースを省略(アロー演算子を省略)
print $$ref_arr[1][2]."\n";
# 別の変数にデリファレンスして代入(アロー演算子を省略)
my @deref_arr = @{$ref_arr};
print $deref_arr[1][2]."\n";
新人「$$ref_arr[1][2]
なんかは多次元配列っぽいアクセスで見やすいですし
この表記で書かれているソースも見た事ありますけど
正直これだけ省略されているというのは意識していなかったかもしれません。
$
を頭に重ねて付けておけばいいや、くらいで…。」
遣付「そうだね。
これはまだ配列の配列だからわかりやすいけど、
配列のハッシュとかになると
もしかするとアロー演算子があった方が見やすいのかもしれない。」
my %hash = (a=>[1, 2, 3], b=>[4, 5, 6], c=>[7, 8, 9]);
my $ref_hash = \%hash;
# 省略なし
print $ref_hash->{b}->[2]."\n";
# 省略あり
print $$ref_hash{b}[2]."\n";
新人「うーん。確かに。」
遣付「ネストしたデータ構造下では、
構造そのものの妥当性や建て付けなど
表記の他にも色々考慮しなくてはいけない事が多い。
(perlref - Perlのリファレンスとネストしたデータ構造)
->
を打つ手間を惜しんだばっかりに可読性が下がって
バグを生み出すリスクが上がってしまうくらいであれば
慣れない内はデリファレンス修飾子やアロー演算子を省略するべきではない
と思うけどね。」
第三条 二項:アロー演算子やデリファレンス修飾子は(できるだけ)省略しないで書く。
遣付「これに関しては、例えば配列の配列のように
直感的になる場合も多々ある。
例えば『ただし配列の配列の場合は中置のアロー演算子は省略する。』など
例外的なルールは後で追記していけばいい。」
新人「よく見る表記だけを真似して書けばいいってもんじゃない、
という事ですね。了解です!」
遣付「うん。サクサクいこう。」
第四条:制御文(条件分岐)はifもしくはunlessとorだけでいい。
遣付「Perlの条件分岐という意味での制御文を挙げるとざっとこれだけある。
が、使うのは基本的にif
とunless
とor
だけで十分だ。」
新人「gotoがまずいってのは何となくわかりますが、
その他はなんででしょうか?」
遣付「その前に、Perlの真偽値について説明しておこう。
Perlでは先に偽の定義があって、偽でないものを真とするんだ。」
数値 0, 文字列 '0' と '', 空リスト (), undef は全て真偽値 コンテキストでは偽となります。 その他の全ての値は真です。
perlsyn - Perl の文法 - 真偽値
新人「あの、真偽値コンテキストって何ですか?」
遣付「スカラコンテキストの一種だと思ってくれていい。
スカラの中身が文字列なら文字列コンテキスト、
数値なら数値コンテキスト、みたいな。
つまり条件式でのコンテキストだな。」
新人「あぁ、つまりはif文の中って事ですね。」
遣付「そういう事だ。
じゃあ上から見ていこう。
if
とunless
は簡単に言うと
『偽でない場合』と『偽である場合』の評価をする制御文だ。」
my $flg = 0;
if($flg){
print "sin\n";
}
unless($flg){
print "gi\n";
}
gi
遣付「数値の0はPerlでは偽となる。
つまり$flg
は真偽値コンテキストでは『偽』だ。
上のif
文は$flg
が偽なので実行されないが
unless
文は$flg
が偽なので実行される、という事だね。」
新人「if
に否定演算子の!
を使ったらダメですか?」
my $flg = 0;
if($flg){
print "sin\n";
}
if(! $flg){
print "gi\n";
}
遣付「この場合ならそれでもいいかもね。
でもPerlには**文修飾子**という文法があってこんな風にもかけるんだ。
後置のif、後置のunlessなんて呼び方もするね。」
my $flg = 0;
print "sin\n" if $flg;
print "gi\n" unless $flg;
print "gi\n" if ! $flg;
# ブロックをつける事もできるけど…
print "gi\n" if(! $flg);
新人「おー。すっきりした。」
遣付「これだとどうだい?
ブロックのカッコがないのもそうだけど
unlessの方が読み易くはないか?」
新人「確かに…」
遣付「偽である事を明確に判定したいというケースも中には存在するはずだ。
特にPerlはTrue/Falseみたいな真偽の二値をもっているわけではなく
偽の定義ありきで、偽でないものが真だからね。」
my $var1;
# var1がundefなら'aiueo'を代入。
$var1 = 'aiueo' unless defined $var1;
# ↓よりみやすくない?
# $var1 = 'aiueo' if ! defined $var1;
# ちなみに「defined-or」を使うと↓のように書ける。
# $var1 //= 'aiueo';
print $var1;
新人「なるほどなるほど。」
遣付「文修飾子の話が出たので、ついでに話しておくと
文修飾子は変数宣言時は使わないようにしよう。」
注意: (my $x if ... のような) 条件構造やループ構造で修飾された my state,our 文の振る舞いは 未定義 です。 my 変数の値は undef かも知れませんし、以前に代入された値かも 知れませんし、その他の如何なる値の可能性もあります。 この値に依存してはいけません。
perlsyn - Perl の文法 - 文修飾子
# これならいいが…
my $var1;
my $var2 = 'aiueo' unless defined $var1;
print $var2."\n";
# 出力「aiueoと改行」
# これだとvar4がundefなのでprint $var4でエラーとなる。
my $var3 = 'kakikukeko';
my $var4 = 'sasisuseso' unless defined $var3;
print $var4."\n";
# 出力「Use of uninitialized value $var4 in concatenation (.)」
# undefと改行を結合しようとして怒られている。
# 例え、print $var4;にしたとしても
# 「Use of uninitialized value $var4 in print」が出て怒られる。
# 要はundefのものを使うんじゃねぇという事だね。
新人「Perlが真偽値のベースとして偽を扱う事は理解しました。
でも、条件分岐がネストする時はifの方が良いのではないでしょうか?」
遣付「素晴らしい。その通りだ。
elsif
はあるけど**elsunless
はないからね。
だからunless
は文修飾子にしか使わない**というルールにした方がよいかもだ。」
# これならいい。
unless($flg == 0){
#(中略)
}
# これもまだいい。
$hoge = 'aiueo' unless defined $flg;
# ただ、こうなっちゃうと…わかりずらい。
unless($flg){
#(中略)
}else{
#(中略)
}
# 素直にifで書いた方がよい。
if(! $flg){
#(中略)
}else{
#(中略)
}
# 何なら条件分岐も増やせるしね。
if(! $flg){
#(中略)
}elsif($flg == 1){
#(中略)
}elsif($flg == 2){
#(中略)
}else{
#(中略)
}
新人「承知しました!
先走ってしまうと、こんな感じでしょうか?」
第四条 一項:条件分岐には基本的にif文を使用する。
第四条 二項:unless文は文修飾子でのみ使用する。
遣付「いいね!いい感じだ。
話が若干逸れたが、if
とunless
はよく使うという所で
次のor
にいこう。」
my $file = "C:\\data\\read_only.txt";
# 特殊変数「$!」はシステムコールエラー。
open(my $fh, "<", $file) or die "Can't open $file:$!";
while(my $line = <$fh>){
print $line;
}
遣付「ファイルオープンする時にはopen
とor
がよくセットで使われる。
何ならファイルオープンのイディオムと言ってもいいかもしれない。」
新人「ファイルが開けなかったらdie
でプログラム終了という事ですね。
でもor
ってそもそもどういう演算子なのでしょうか?」
遣付「前に出てきたnot
と同じで、優先順位がとても低い演算子なんだ。
しかもnot
より低く、演算子の中でも最低の部類だ。
こうしてor
の左項の式評価が偽の場合、
右項が実行(評価)されるという塩梅になる。」
新人「なるほどー。」
遣付「逆に、このイディオムの他で使うかと言うと
すぐには出てこないので、とりあえずこれだけ書いておこう。」
第四条 三項:or文はファイルのopenイディオムでのみ使用する。
遣付「次は条件演算子か。他の言語では**三項演算子**なんて呼ばれていたりもするね。」
# 普通の条件演算子
my $input1 = 0;
my $output1 = $input1 ? 1 : 3;
print $output1;
print "\n";
# 条件演算子のネスト
my $input2 = 2;
my $output2 = $input2 == 1 ? 2 : $input2 == 2 ? 3 : 4;
print $output2;
新人「えっと…えっと…出力はどちらも3ですね。」
遣付「そう。条件演算子はこのようにネストもできるんだけど
ちょっと見づらいよね。
これならif文で書いた方がよっぽど見やすい。」
my $input = 2;
if($input == 1){
$output = 2;
}elsif($input == 2){
$output = 3;
}else{
$output = 4;
}
print $output;
新人「場合によりけり、ですかね?」
遣付「そうだね。
真偽値判定だけで単純な条件演算子ならよいかもだけど
複雑なのはあまりよくないと思う。」
新人「わかりました!こうですね?」
第四条 四項:条件演算子(三項演算子)はもし使うならシンプルに。
遣付「うん…曖昧過ぎる気もするが、まぁいいか。」
新人「はい!楽しくなってきました!」
遣付「いいぞ。じゃあ、条件分岐はこの辺でいいか。」
新人「はい!って、あのswitch
とgiven
とgoto
は…?」
遣付「使わない。以上だ。」
新人「えぇ…」
遣付「じゃあ、触りだけ。
switch
はモジュールで、given
は構文なのでモノとしては別の物だが
どちらもいわゆるSwitch文だ。
『perl switch given 性能』でググればわんさか出てくるのでわかるが
とにかくifより遅いんだ。
使う必要はない。
とりあえずPerldocを引用してツッコミを入れておこう。」
このいかすコードのどこかに間違いなく重大なバグが潜んでいる**(笑)** バグレポートやフィードバックは大歓迎。
Switch - Perlのswitch文
⇒何わろとんねん。
given と when の実験的な詳細
既に述べたように、"switch" 機能は非常に実験的であると考えられています; ほとんど知らせることなく変更されることがあります。 特に when はトリッキーな振る舞いがあり、将来よりトリッキーで なくなるように変更される予定です。 現在の(誤)実装に依存しないでください。 Perl 5.18 より前では、given には、コードが古いバージョンの Perl でも 動かなければならない場合に気をつけるべきトリッキーな振る舞いもあります:
perlsyn - Perl の文法 - given と when の実験的な詳細
⇒何回トリッキー言うねん。
遣付「gotoはそうだな…構造化プログラミングとgoto文の
Wikipediaでも読んでおけばいいだろう。」
新人「あっさりですね…」
遣付「あぁ、あまり関わりたくないからな…」
第五条:制御文(ループ)はforかwhileでいい。
遣付「さぁ、次はループ文だ。ざっとこれだけある。」
ループ文
- for
- foreach
- while
- until
- do while(厳密には文ではなくdoブロックに対して文修飾子whileを付けたもの)
- while eachイディオム
ループ制御文
- last
- next
- redo(厳密には文ではなくredo関数)
- continue(ブロックありきで実行制御文扱い)
遣付「ループは絶対に触るものだしサラッといこう。
まず、for
とforeach
は等価だ。
Perldocに明記されるようになったのは5.16.1からっぽいけど
5.10.1でも同様に動くので、まぁそういう事なんだろう。」
foreach は実際には for の同義語なので、どちらでも使えます。
perlsyn - Perl の文法 - foreachループ(5.16.1)
遣付「事実、以下のコードは1~3までの数字をprintするコードだが
全てfor
でもforeach
でも書ける。」
# foreachに変えても同じ
for(my $i=1; $i<4; $i++){
print $i;
}
# foreachに変えても同じ
for my $j (1, 2, 3){
print $j;
}
# foreachに変えても同じ
my @arr = (1, 2, 3);
for my $k (@arr){
print $k;
}
# foreachに変えても同じ
for my $h (1 .. 3){
print $h;
}
# foreachに変えても同じ
for (1 .. 3){
print $_;
}
新人「Perldocにはこんな記述があるのですが
foreach
との使い分けは不要でしょうか?」
変数の前にmyというキーワードが置かれていた場合、その変数はレキシカルスコープを持ち、したがってそれはループの中でのみ可視となります。 このキーワードがなければ、変数はループに対してローカルとなり、ループを 抜けた後で以前の値が再度取得されます。 変数が事前に my を使って宣言されていたならば、グローバルなものの 代わりにその変数を使いますが、それもループにローカルなものとなります。 この暗黙のローカル化は foreachの中でのみ 起きます。
perlsyn - Perl の文法 - foreachループ
遣付「うん。等価の方が強い。
要はfor VAR (LIST)
で書いたら、という事だね。
VAR
がmy
付きならそれはループ内でのレキシカル変数という事になる。」
新人「はぁ、本当に同じなんですね。」
遣付「そうなんだ。
人によっては慣習的に配列はforeach
で回したいという人もいるかもだけど
とりあえずfor
に統一しよう。」
第五条 一項:foreachは使わない。
遣付「それとこうだ。」
第五条 二項:for文のVARは省略しないで必ずmyをつける。
新人「これは…?」
遣付「test15.plではfor
とforeach
が同じ事を示すために
あえてVAR
を省略して書いてみたけど、
VAR
を省略すると$_
で受けなくてはいけない。
これは可読性が良いとは言えないね。」
# $_はやめよう?
for (1 .. 3){
print $_;
}
# 面倒くさくてもレキシカルスコープを持つ変数をmyで宣言して運用しよう。
for my $i (1 .. 3){
print $i;
}
新人「ではmy
をつけない形はどうですか?」
my $i;
for $i (1 .. 3){
print $i;
}
遣付「この記述だね。
このキーワード(my
)がなければ、変数はループに対してローカルとなり、ループを 抜けた後で以前の値が再度取得されます。
perlsyn - Perl の文法 - foreachループ
うーん。
それでも動くんだけど余程の事情がない限りナシだねぇ。
初期化していないように見えるのに、普通に動くのが気持ち悪いし
明示的に初期化したとしても、途中で値が変わるっていうか
二重の用途で変数を運用しているように見えるし。」
my $i;
for $i (1 .. 3){
# 1~3を出力
print $i;
}
# ↓はエラーになる
# (初期化されていないから)
# (for文によって代入された1~3の値のライフサイクルはfor文の中でだけ有効)
#print $i;
# じゃあ初期化してみよう。
my $j = 0;
for $j (1 .. 3){
# 1~3を出力
print $j;
}
# 0を出力。(不自然じゃない?)
print $j;
新人「なるほど。理解しました。」
遣付「よし。じゃあwhile
だ。
while
はファイルをオープンするコードでも見たね。」
my $file = "C:\\data\\read_only.txt";
# 特殊変数「$!」はシステムコールエラー。
open(my $fh, "<", $file) or die "Can't open $file:$!";
while(my $line = <$fh>){
print $line;
}
遣付「for
と違ってwhile
はループ条件だけで動く。
while(EXPR)
でEXPR
が真なら動き続けるという形だ。
シンプルだからこそ意図せぬ無限ループには気を付けないといけない。」
新人「ふむふむ。」
遣付「上のファイルオープンのようにループ対象が有限であればよいが
無限のものを扱う場合はループを抜ける設計が必須となる。
ループ制御については後で説明しよう。」
新人「わかりました。」
遣付「while
と似たような制御でuntil
がある。
これはuntil(EXPR)
でEXPR
が偽なら動き続けるという形だ。
if
に対するunless
みたいなもんだね。
ただwhile
とfor
で代用できるから無理に使う必要はない。」
新人「for
とwhile
だけの方がまぁシンプルだと思います。」
遣付「えっと、次はdo while
とwhile eachイディオム
だね。使わない。」
新人「また!?思いの外、説明がサラッといかなくて
ちょっと飽きていませんか?」
遣付「まさか、そんな事はないよ…しょうがない。
じゃあ簡単に説明しよう。
until
と同様にfor
とwhile
で代用が効く
というのも使わない理由としては大きいけど
do while
はループのような動作はするが、厳密にはループ文ではない。
doブロック文
に文修飾子のwhile修飾子
を付けただけだ。
だからループ制御文も効かない。論外。
do BLOCK はループとしては 扱われません; 従って、next, last,redo といったループ制御文は ブロックから抜けたり再開することはできません。
perlfunc - do
はい。あとwhile eachイディオム
ね。
主にwhile
と組み合わせてイテレータとして使うとわかりやすい。
while(my ($k, $v) = each(%hash)){
print "key=$k, value=$v¥n";
}
each
は関数だね。
ハッシュに対してリストコンテキストで呼び出した場合は、次の要素に対する、 ハッシュのキーと値を返します。 Perl5.12以降でのみ、配列のインデックスと値からなる 2 要素のリストを返すので、反復を行えます; より古い Perl ではこれは 文法エラーと考えられます。 スカラコンテキストで呼び出した場合は、 ハッシュの場合は(値ではなく)キー、配列の場合はインデックスを返します。
perlfunc - each
元々はハッシュの為の関数だったけど、5.12以降は配列でも使えるんだ。
どちらかというと配列で使いたかったのに
この現場は5.10.1だから…(震え声)」
# Perl5.12以降でのみ使用可能
while(my($i, $v) = (@arr)){
print "index=$i, value=$v¥n";
}
新人「自分が使えないからって、すねないでください。」
遣付「冗談はさておき
Perl言語に慣れるまでは
普通のfor
と普通のwhile
で十分というのが伝えたかった事だ。
ただwhile eachイディオム
は本当に便利なので
後でちゃんと標準化してもいいかもね。」
第五条 三項:untilは使わない。
第五条 四項:do whileは使わない。
第五条 五項:while eachイディオムは使わない。(改訂の余地あり)
新人「わかりました!」
遣付「じゃあ、ループ制御文にいこう。」
my @arr = (1, 2, 3, 4, 5);
for my $v (@arr){
if($v == 1){
next;
}
print $v;
if($v == 4){
last;
}
}
# 234を出力して終了
遣付「このように
ループを抜ける際はlast
**ループを次の周に移すにはnext
**を使う。
while
で話した無限ループの終了条件についても話そう。
無限ループを前提として建て付けた次のコードを見てほしい。」
my @arr = ();
my $str = 'A';
# 無限ループ
while(1){
# @arrの最後のインデックスが4になったらループを抜ける。
if($#arr == 4){
last;
}
# @arrに対してインデックスを増やしながら要素を追加していく。
push @arr, $str++;
}
print @arr;
# ABCDEを出力して終了
新人「サラッと書かれてますけど、push
し忘れたら無限ループですね。」
遣付「そう。だから無限ループ前提でループを回す際は
**『lastをコールしているか?』**だけでなく
**『lastがコールされる条件が確実に収束するか?』**という確認が超重要だ。
だから無限ループを使わなくてよいのであれば、無理に使うのはやめよう。
上みたいにイキって書かなくても、次のようにfor
で十分だ。」
my @arr = ();
my $str = 'A';
for(my $i=0; $i<5; $i++){
push @arr, $str++;
}
print @arr;
# ABCDEを出力して終了
新人「面白味はないですが確実なのが一番ですね。」
遣付「その通り。じゃあ、ループ制御文はこれで…」
新人「待った!
redo
とcontinue
を使わないのは何となく察しましたから
せめて使わない理由を!」
遣付「…わかったよ。
redo
は、next
のように次の周に移るのではなく
現在の周をやり直すんだ。
redo コマンドは、条件を再評価しないで、ループブロックの 始めからもう一度実行を開始します。
perlfunc - redo
動きとしてはgoto
だよね。だから使わない。
で、Perlのcontinue
は一般的なcontinue文と違って
コマンド文ではなく、ブロックを伴う実行制御文として動作する。
BLOCK が引き続く場合、continue は実際には関数ではなく、 実行制御文です。 continue BLOCK が BLOCK (典型的には while または foreach の中)にあると、これは条件文が再評価される直前に常に実行されます; これは Cにおけるforループの3番目の部分と同様です。
perlfunc - continue
while (EXPR) {
### redo always comes here
do_something;
} continue {
### next always comes here
do_something_else;
# then back the top to re-check EXPR
}
### last always comes here
Perldocの説明にある通り
ループ条件の評価前に実行する動作を記述するのがcontinueブロック
だ。
ブロックで定義しないといけないほど評価前の処理を書かなくてはいけない
という状況がそもそもループロジックの建て付けとしておかしい気がするよね。
だから使わない。」
新人「そんなに、まくしたてなくても…」
遣付「面倒くさくはなっていないぞ。本当だぞ。
よし。まとめだ。」
第五条 六項:ループを実現する際はfor文かwhile文を使用する。(冗長記載だけど明記しておく)
第五条 七項:無限ループを実装する場合は、ループ終了条件が収束するように確実に設計する。(具体策は別途、考えよう)
第五条 八項:ループ制御文はnextとlastのみを使用し、redoとcontinueは使わない。
新人「ループはfor
とwhile
だけ…
ループ制御はnext
とlast
だけ
…うん!シンプルですね。」
遣付「よかった。次にいこう。」
第六条:文字列の取り扱いを決めよう。
遣付「まず**文字列とは?**からいこうか。」
文字列リテラルは、シングルクォートかダブルクォートで区切られます。 これらは、標準 Unix シェルのクォートと同じように扱われます: ダブルクォートの文字列リテラルでは、**バックスラッシュの置換と 変数の置換が行なわれ、**シングルクォートの文字列では、 (' と \を除いて)これらの置換は行なわれません。
perldata - Perl のデータ型 - スカラ値のコンストラクタ
遣付「'
もしくは"
で括られたモノが文字列という事だね。
で、"
の場合は変数の置換が行われるとある。
つまりこういう事だ。」
my $whatis = 'pen';
print "This is a $whatis";
遣付「この"
の中で変数が置換される事を**文字列補間**と呼ぶ。」
新人「変数展開ではないんですか?」
遣付「変数展開でも合っているよ。
文字列補間の方がより広義な呼称だね。
ただ、変数展開の方が言葉の響き的にしっくり来るし、
一般的に受け入れられているみたいだから
ここでの呼び方は変数展開にしようか。」
新人「了解です。」
遣付「で、連続した文字列の中で変数展開する際は${}
を使う。」
いくつかのシェルと同じように、変数名の前後に中かっこを入れて、つながっている英数字(および下線)から切り離せます。 変数を文字列に展開する時に、後に続くコロン 2 つやシングルクォートと 変数名を分割する場合にもそうしなければなりません; さもなければパッケージのセパレータとして扱われるからです:
perldata - Perl のデータ型 - スカラ値のコンストラクタ
$who = "Larry";
print PASSWD "${who}::0:0:Superuser:/:/bin/perl\n";
print "We use ${who}speak when ${who}'s here.\n";
遣付「つまり変数名の後に続く文字列と切り離したい時だね。」
# 「FooBarBaz」と連続した文字列を出力したい
my $foo = 'Foo';
my $baz = 'Baz';
# ↓はエラーとなる。$fooBarまでを変数として展開しようとするから。
# print "$fooBar$baz";
# これならOK。
print "${foo}Bar$baz";
# bazは冗長でも、「${}」で揃えた方がまだ見やすい。
print "${foo}Bar${baz}";
新人「へぇ。ブレース{}
の中に記号は要らないんですね。」
遣付「シェルで慣れていない人にとっては**ん?**って思うかもしれないね。
まぁ慣れだ。」
新人「了解です!」
遣付「じゃあ第三条の振り返りをしようじゃないか。」
第三条 一項:ハッシュのキーはクォートしない。
新人「そんなケースがあるかはわかりませんが、
クォートしたい場合はどうしましょうね。」
my %hash1 = ('a', 1, 'b', 2, 'c', 3);
my %hash2 = (a=>1, b=>2, c=>3);
my %hash3 = ('a'=>1, 'b'=>2, 'c'=>3);
my %hash4 = ("a"=>1, "b"=>2, "c"=>3);
遣付「これだね。」
新人「そうでした。
という事は、%hash4
のハッシュキーは変数展開できるという事でしょうか?」
my $key_a = 'a';
my %hash4 = ("$key_a"=>1, "b"=>2, "c"=>3);
# もしくは冗長だけどこう。
my %hash5 = ("${key_a}"=>1, "b"=>2, "c"=>3);
遣付「その通り。」
新人「うーん。
動的にハッシュキーを設定するというケースも
絶対にないとは言い切れないですよね。
でも、ハッシュキーをクォートしないというルールに抵触してしまう…」
遣付「じゃあ、早速だけど、第三条一項を微調整して改訂しよう。」
新人「いきなりですか?」
遣付「なに、大した事じゃない。曖昧だった部分を補強するだけさ。
よく見てほしい。
最初に決めた時に扱ったコードも、今見ているコードも
ハッシュの初期化子だよね?」
新人「あ。確かに。」
遣付「という事はだ。
空ハッシュとして初期化しておいて値を詰める形でも
大筋の初期化という意味では同じだよね。」
# 初期化子を使って初期化
my %hash1 = (a=>1, b=>2, c=>3);
# 空ハッシュで初期化
my %hash2 = ();
$hash1{a} = 1;
$hash1{b} = 2;
$hash1{c} = 3;
新人「…!という事は…
これでどうですか!?」
第三条 一項(改訂):ハッシュの初期化子において、キーはクォートしない。
遣付「うん。いいと思う。
ハッシュの動的キーについてはどうする?」
新人「初期化子でやらないとなると…
2通りのやり方が出てきてしまうんですが
どっちが良いでしょうか?」
# 「XXXXBarXXXX」というハッシュキーを設定したい。
# 「XXXX」は動的な文字列。
my %hash = ();
my $prefix = 'Foo';
my $suffix = 'Baz';
# とりあえず「FooBarBaz」というキーを設定しよう。(値は1)
# やり方A: 文字列連結演算子
$hash{$prefix.'Bar'.$suffix} = 1;
# やり方B: 変数展開
$hash{"${prefix}Bar${suffix}"} = 1;
遣付「あくまで個人的な考え方だけど、
文字列のみの連結であれば変数展開が望ましいと思うね。
何故なら、関数を挟む場合には文字列連結演算子.
の方が見やすいからだ。
# 「HEADXXXX_XXXX_XXXX」というハッシュキーを設定したい。
# 「XXXX」は動的な文字列。
# 結合対象のデータ(プレフィックスと配列)
my $prefix = 'HEAD';
my @arr = ('Foo', 'Bar', 'Baz');
# $keyに「HEADFoo_Bar_Baz」を設定。
my $key = $prefix . join('_', @arr);
# ハッシュキーをセット
$hash{$key} = 1;
新人「なるほど!
加えて、キーが長くなる場合は、変数は一つ増えますけど
キーに文字列を直接的に設定しない方が見やすいは見やすいですね。」
遣付「さぁ、まとめて最後までやっちゃおう。
文字列の標準化はどうなる?
ハッシュキーのクォートとも整合性が合うか確認してみよう。」
新人「わかりました!
これがこうなって…これで如何ですか!?」
第三条 一項(改訂):ハッシュの初期化子において、キーはクォートしない。キーを動的にしたい場合はハッシュを初期化したあとに行う。
第六条 一項:文字列変数のみを引用する場合は変数展開${}
を使用する。
第六条 二項:文字列変数に加えて関数を引用する場合は変数展開と合わせて、文字列連結演算子.
を使用する。
第六条 三項:変数展開を行う際に、見通しがよくなるのであれば冗長であっても${}
を使う。
第六条 四項:ハッシュの代入において、キー値に変数展開を用いてもよいが、見づらくなるようであれば、キー値用のスカラ変数を別途用意する。
遣付「お見事!」
新人「えへへ。」
遣付「じゃあ、クォート風演算子についてもついでに説明しておこう。
Perlはクォート文字だけではなく
クォート風演算子という別の汎用的な記法を持っている。
クォートはリテラル値であると考えるのが普通ですが、Perl において、 クォートは演算子として働き、さまざまな展開やパターンマッチの機能を 持っています。 そのような動作をさせるのに、Perl は慣習的にクォート文字を使っていますが、 どの種類のクォートも、自分でクォート文字を選べるようになっています。 以下の表では、{} がその選んだ区切文字のペアを示しています。
[perlop - Perl の演算子と優先順位 - クォートとクォート風の演算子](https://perldoc.jp/docs/perl/5.26.1/perlop.pod#Quote32and32Quote-
like32Operators)
通常記法 | 一般的な呼称 | 汎用記法 |
---|---|---|
'' | シングルクォーテーション | q{} |
"" | ダブルクォーテーション | qq{} |
※抜粋 |
遣付「つまり、''
と""
のリテラルはそれぞれ次のようにも書ける。」
my $str1 = 'aiueo';
my $str2 = q{aiueo};
print "Equal\n" if ($str1 eq $str2);
# Equalを出力
my $ve = 'Bar';
my $str3 = "Foo${ve}Baz";
my $str4 = qq{Foo${ve}Baz};
print "Equal\n" if ($str3 eq $str4);
# Equalを出力
# 実は区切り文字はブレースでなくてもいい。
my $str5 =q(aiueo);
my $str6 =q/aiueo/;
my $str7 =q#aiueo#;
my $str8 =q!aiueo!;
my $str9 =q[aiueo];
my $str10 =qq(Foo${ve}Baz);
my $str11 =qq/Foo${ve}Baz/;
my $str12 =qq#Foo${ve}Baz#;
my $str13 =qq!Foo${ve}Baz!;
my $str14 =qq[Foo${ve}Baz];
遣付「中でも特殊なのは単語リスト演算子qw{}
だな。
これは''
リテラルを要素とするカンマ区切りの文字列リストを表現する。
配列の初期化においては、
カッコやらシングルクォーテーションやら
カンマやらを省略できる、というのが強みではあるのだが…」
# 以下は全て同じ配列
my @arr1 = ('Aa', 'Bb', 'Cc', 'Dd');
my @arr2 = qw(Aa Bb Cc Dd);
# 区切り文字にはカッコ以外も使用可能。
my @arr3 = qw/Aa Bb Cc Dd/;
my @arr4 = qw{Aa Bb Cc Dd};
my @arr5 = qw#Aa Bb Cc Dd#;
my @arr6 = qw!Aa Bb Cc Dd!;
my @arr7 = qw[Aa Bb Cc Dd];
新人「うーん、何か区切り文字が何でもいいって所が気になりますね…」
遣付「そうなんだ。
配列だけでなく単純リテラルの文字列リストを扱う場面においては
強いことは明らかなんだけど、
使うならもっと細かく条件付けして
明示的に標準化した方がよい気がするね。」
新人「…先送りにしますか。」
遣付「…そうだね。」
第六条 五項:クォート風演算子は使わない。(暫定)
遣付「もし正規表現を触る事が増えてきたら
振り返りの意味もこめてクォート風演算子をきっちり標準化しよう。
あと、ヒアドキュメントなんかも使いようによっては強力だ。
クォート風演算子のそれぞれの細かい挙動については
Perldocに載っているから興味があったら見ておいてくれ。」
perlop - Perl の演算子と優先順位 - クォート風演算子
新人「わかりました!」
第七条:変数名は確実に命名しよう。
遣付「基本中の基本だな。
大筋としてPerlに限った内容ではないけど書いておこう。」
新人「命名は気にしているつもりですが…
あ!この**記事**がとても参考になりました!」
遣付「どれどれ…うん、説明を続けよう。」
新人「(無視された…)」
遣付「『変数』で一番大事な概念は何より**『名実一体』**であることだ。」
新人「(書いてること、同じじゃん!)」
遣付「何の用途で、中にどんな値が入ってて、
そしてどのように扱い、適用し、加工し
変数としての役割をどう全うするのか、
つまり、スコープやライフサイクルだな、
そこらへんを踏まえて、**この変数は何者か?**というのが
できる限り一目で認知されるよう命名をすべきだ。」
新人「な、なるほど。わかります。」
遣付「まぁ$tmp
とか曖昧な変数を広いスコープで気軽に使うな、とか
ハンガリアン記法はやめろ、とか
汎用的に細かい事を言い出すとキリがないので
Perlに特化した超基本を2つだけ決めておこう。」
新人「変数の標準化って何か大事な位置づけっぽいですけど…
2つだけですか?」
遣付「最初はそれでいい。
後で必要に応じて適宜追加していこうじゃないか。」
新人「了解です。」
遣付「まず一つ目。」
第七条 一項:同じ変数名を使わない。
新人「???どういうことですか?」
遣付「これを見てくれ。」
my $tmp = 1;
my @tmp = (2,3,4);
# 1を出力
print $tmp;
# 234を出力
print @tmp;
# 2を出力
print $tmp[0];
新人「えぇぇぇぇ!!Sigilが違うと別の変数なんですか?」
遣付「そうだ。怖いだろう?」
新人「おっかねぇ…知りませんでした。」
遣付「なのでどんなに小さいスコープであっても
同名か同名でないかは確認するべきだ。
思わぬ事故に繋がりかねない。」
新人「気を付けます。」
遣付「じゃあ二つ目。」
第七条 二項:一文字の変数は使わない。
新人「…説明を貰えますか?」
遣付「君が1か月前に書いたサンプルコードを見てみよう。」
my $a = 0;
my $b = 1;
# 01を出力
print '' . $a . $b;
print "\n";
新人「わぁ、懐かしい…(しみじみ)
あの、これが何か?」
遣付「0点だ。」
新人「手厳しい!」
遣付「このコードに追記してみよう。」
my $a = 0;
my $b = 1;
# 01を出力
print '' . $a . $b;
print "\n";
my @arr = (4, 9, 2, 8, 0, 1, 5, 6, 3, 7);
for my $v (sort {$a <=> $b} @arr){
print $v;
}
新人「一体、何がまずいって言うん…あっ!」
遣付「気付いたか。
sort
イディオム中で使われる$a
と$b
はグローバル変数だ。
上のコードを動かしても期待通りにソートされない事がわかる。」
01
4503261798
新人「そうか…この$a
と$b
は何だろう?と思いながらも
何となくソートを使っていましたが、グローバル変数だったんですね。」
遣付「そう。無論、君のサンプルコードがなければ
このコードは正常に動く。」
my @arr = (4, 9, 2, 8, 0, 1, 5, 6, 3, 7);
for my $v (sort {$a <=> $b} @arr){
print $v;
}
0123456789
遣付「sort
についてはPerldocも合わせて確認しておくといい。」
perlfunc - sort
perlop - 等価演算子
新人「ソートについての標準化はしないのですか?」
遣付「なかなか根深いテーマなんでな…泣く泣く割愛させてもらう。」
新人「(めんどいんだな…)」
遣付「さて、話を戻そう。
アルファベット一文字でグローバル変数として扱われるのは
Perl本体では$a
と$b
だけだと思うが、
例えばCatalystフレームワークにおいては
慣習的に$c
がコンテキストオブジェクトを示す変数だったりする。」
新人「はぇ~。」
遣付「加えて、Perlにはアルファベット一文字の予約語が非常に多い。
演算子然り、コマンドのスイッチオプション然り。
演算子なら例えば、繰り返し演算子x
とか
正規表現のクォート風の演算子の置換s///
とかね。」
新人「言われてみると確かにそうですね。」
遣付「じゃあ、**二文字以上ならいいのか?**という話だけど
例えば文字列比較演算子のeq
とかne
とかは二文字だし、
cmp
は三文字だし…という事でキリがないので
とりあえず一文字変数だけを縛る事にしよう。
$a
と$b
だけでもいいかもだけどね。」
新人「二文字もいずれ…ですかね。」
二文字の演算子一例
eq
ne
lt
le
gt
ge
遣付「だね。じゃあ次に進もう。」
第八条:関数とサブルーチンの棲み分けをしておこう。
遣付「いよいよサブルーチンに足を突っ込んでいくわけだが…
関数とサブルーチンの違いはわかるかな?」
新人「えっと、サブルーチンは自分で作ったコード、という事ですかね?」
遣付「実は、曖昧なんだ。」
明確な return 文のない関数はしばしばサブルーチンと 呼ばれますが、Perl の定義上はこれらの間に何の違いもありません
perlsub - Perl のサブルーチン - 説明
新人「おぉ!そうなんですね。」
遣付「ただ、こうも書いている。」
多くの言語と同様、Perl はユーザー定義のサブルーチンを提供しています。 これらのサブルーチンは、メインプログラムのどこにでも置くことができ、 do, require, use といったキーワードを使って他のファイルから ロードすることができ、eval や無名サブルーチンを使って 生成することもできます。 サブルーチンの名前や、コードリファレンスを保持する変数を使って 間接的に関数を呼び出すことも可能です。
perlsub - Perl のサブルーチン - 説明
遣付「解釈の仕方は人それぞれかもしれないが、
ここでは便宜上こう定義しておこう。」
第八条 一項:自分で作成した関数を『サブルーチン』と呼称する。
第八条 二項:perlfuncで規定されるAPI関数、およびモジュールないしサードパーティ製のライブラリで提供される関数を『関数』と呼称する。
新人「なるほど。自分で作った物がサブルーチンと。わかりやすいですね。」
遣付「ただ、ここのような大規模開発プロジェクトだと
そこそこのボリュームのAPI基盤を内生することもある。
それを『サブルーチン』と呼ぶか、『関数』と呼ぶかは意見が分かれるところだね。」
新人「ふむふむ。
自分たちで作ったものであっても
基盤レベルまでAPIが昇華されているのなら『関数』と呼んだ方が適切かもしれない
という事ですね。」
遣付「そうだね。
まぁ今はこんなもんでいいだろう。
次にいこう。」
第九条:サブルーチンの役割と用途は事前に打ち出そう。
遣付「自分で作る関数をサブルーチンと呼ぶと決めた所で質問だ。
サブルーチンを作る理由は何だ?」
新人「理由…ですか?」
遣付「そう。」
新人「えっと…メインのサブルーチンが長くなってしまうから…とかですかね。」
遣付「それも理由の一つだろう。あとは?」
新人「うーんと、再利用性の高い部品を作っておくと流用が効くから…?」
遣付「それから?」
新人「えっと、えっと…」
遣付「そうなんだ。
実はとても大事なサブルーチンの大前提に気付いていない人は結構多い。
構造化プログラミングの原理に則って
順次、選択、反復の制御フローをブロック単位で小分けにして
全体的なアルゴリズムの見通しをよくしたり、
時には再利用性を高めたり、
スケーラビリティを担保したり、
というのは確かにサブルーチンの大きな作用であり恩恵だ。
無論、その恩恵を得るために
サブルーチンを作るというのはアプローチとしては決して間違っていない。
が、それは必要条件であって、十分条件ではないんだよ。」
新人「???どういう事ですか?」
遣付「二つの命題p
とq
に対して、pならばqである
が成り立つ時
p
を十分条件、q
を必要条件と呼ぶ。
pサブルーチンを作る然るべき理由がある。
ならば
qサブルーチンを作ったら再利用性が高まったり、見通しがよくなったりした。
なんだ。
また、pならばq
かつqならばp
が成り立つ状態を
pとqは同値である
と呼ぶ。
このp
とq
が同値である時のp
を必要十分条件と言うんだ。」
新人「必要十分条件…」
遣付「サブルーチンを作る理由が必要十分条件であるべきなのは言うまでもない。
その理由が適切であったかどうかを振り返って評価する事もできるからね。
qサブルーチンを作ったら再利用性が高まったり、見通しがよくなったりした。
ならば
pサブルーチンを作る理由は適切であった(作成判断は正しかった)
これはPDCAの観点から見ても正しいアプローチと言えそうだ。」
新人「なるほどー。」
遣付「では然るべき理由とは何か?
理由は大きく二つ。」
新人「二つしかないんですか?」
遣付「あぁ。**そんなの当たり前じゃん?**と思う事かもしれないけど
あえて声を大きくしておこう。
まずは一つ目。」
第九条 一項:既に存在するサブルーチンは作らない。
遣付「要は**なかったら作る。**当たり前だね。」
新人「じゃあ、サブルーチンを長くしない為に作るサブルーチンも
ないから作るという理解で合っていますか?」
遣付「その通り。
業務仕様的なサブルーチンの建て付けは
API基盤でも存在しない限りは作るしかないからね。」
新人「なるほど。
では、ユーティリティ的なサブルーチンはどう捉えればよいでしょうか?
ハッシュ関数のように一意な値を取得したり
日付や数値など特定の仕様に基づいて加工したり
という用途ですが…」
遣付「それが二つ目の理由だ。」
第九条 二項:既に存在する関数ないしサブルーチンであっても、役割と用途を改善・拡張する形であれば、あらたにサブルーチンを作ってもよい。
新人「??」
遣付「じゃあわかりやすく関数からいこう。
PerlのAPIとして存在する関数があるとする。
例えばpush
にしようか。」
遣付「push
は第一引数に配列、第二引数にリストを取って
対象の配列にリストを追加し、追加された後の配列要素数を返却する
という関数だ。」
my @arr = ();
push @arr, '1';
my $len = push @arr , ('2', '3');
# 配列の中身「123」を出力
print @arr;
print "\n";
# 配列の長さ「3」を出力
print $len;
新人「へー。
第二引数は厳密にはLIST
なんですね。
あと、要素数を返す作用も知りませんでした。
てっきり配列にスカラを追加するだけの関数かと…。」
遣付「既存のコードでもpush ARRAY, SCALAR;
で
リストも渡さないし、戻り値を受け取らないケースがほとんどだから、
そう思うのも仕方ない。
ただね、何かの勘違いで
君がpush
的なサブルーチンを自分で構築しちゃったとしよう。
恐らくその認識下では
第二引数はスカラしか許容しないし
戻り値も存在しないというデグレードしたpush
が出来上がるはずだ。」
新人「あぁ…確かに。」
遣付「たとえ、デグレードしない形で作れたとしても
既存の関数ないしサブルーチンと同じものを作る時間はムダでしかないよね。
なので、サブルーチンを構築する前には
**同じものがあるかないか?**を必ず確認する癖をつけよう。」
新人「わかりました。」
遣付「同じものがあるかないかの判断材料になるのは
冒頭の記載通り、役割と用途だ。
自分が作ろうと思っているサブルーチンは
何を引数に取って
何に対して何を施し
何を返すサブルーチンなのか?
という事を示すのが役割だ。
結果的に、そのサブルーチンが新たにできる事で
どう嬉しくてどのシーンで使える?というのが用途だ。」
新人「なるほど。
じゃあ例えば既存の関数ないしサブルーチンに
不足している致命的な機能があった場合に
機能追加する形であらたにサブルーチンを作るのはアリって事でしょうか?」
遣付「そうだね。
でもその場合、既存のものを修正できないという根拠もある程度は必要だ。
関数であればどうしようもないが
既存のサブルーチンであれば、修正するという指針もあるはずだ。
もし、修正できないとするのであれば
例えば、修正した場合の影響範囲・リスクが大き過ぎる、などの
理由付けは必要だろう。」
新人「あぁ。
確かに既存サブルーチンを参照している側で
デグレが起きる可能性もなくはないですもんね。」
遣付「その通り。
つまりは、だ。
開発のスキームにおいては役割と用途を拡張するのがサブルーチンなんだ。
既にあるものは作る必要ないし
逆になければ作る、非常にシンプルだよね。
だからどんなに小さなサブルーチンであっても
既存のありなしを判定する意味でも
サブルーチンを作成する前にその役割と用途を洗い出すべきだ。」
新人「なるほど!腑に落ちました!」
第十条:サブルーチンの引数はわかりやすくそしてシンプルに。
遣付「じゃあ早速サブルーチンを作成していくよ。
戻り値は次の条で詳しくやるので、
ひとまずここでは戻り値を考えない事にする。
サンプルとは言え、せっかくなので役割と用途を書いておこう。
役割:引数を複数受け取ってその内容をprint
する。
用途:サンプル実行のため。(業務では使用しない)
新人「この複数が苦手なんですよね…」
遣付「まぁまぁ。一つ一つ、段階を踏んでやっていこう。
まずはperlsubを確認してみるよ。」
Perl での関数呼び出しと戻り値のモデルは単純です。全ての関数は引数を、 平坦で一つのスカラのリストで受け取り、同様に全ての関数は呼び出し元に 対して平坦で一つのスカラのりストで返すというものです。
perlsub - Perl のサブルーチン - 説明
遣付「続いてこうある。」
ルーチン(※)に渡されるすべての引数は**配列 @_**に置かれます。
perlsub - Perl のサブルーチン - 説明
※著者注:たぶんサブルーチンの誤訳。原文「Any arguments passed in show up in the array @_. 」
遣付「つまり引数の基本はスカラの配列なんだ。」
sub sample{
print @_;
}
# 1234を出力
sample(1, 2, 3, 4);
my @arr = (5, 6, 7);
# 567を出力
sample(@arr);
新人「なるほど。
リスト
を受け取って@_
配列に格納される、という事ですね。
サブルーチンを呼び出す際のカッコは必須ですか?」
# カッコなしでも呼べるけど…
sample @arr;
遣付「必須ではないけどカッコがあった方がいいね。
慣習的に関数呼び出しはパラメータをスペースで区切る
というのは前に言った通りだけど
カッコがあるだけで関数ではなくサブルーチンというのがわかるし。
特別な事情がない限りはカッコを付けるようにしよう。」
第十条 一項:サブルーチンの呼び出し時はカッコを付ける。subroutine(PARAM)
新人「そういえばPerlには**名前付き仮引数**という概念はないのでしょうか?」
遣付「うん。version5においては基本的にはないという認識でいい。
version6はあるみたいだけど…。
version5.20以降だと一応、シグネチャという機能はあるんだけど
これも実験的なAPIだから使わないのがベターだろうね。
**実験的な機能 (後述の "Signatures" 参照) を除いて、Perl は名前付き仮引数を 持っていません。**my() にこれらのリストを代入することで行えます。
perlsub - Perl のサブルーチン - 説明
遣付「あと名前付き仮引数ではないけど、プロトタイプという考え方がある。」
Perl はとても限られた形のコンパイル時引数チェックに対応しています。 これは PROTO セクションか プロトタイプ属性 で宣言できます。 以下のどちらかのように宣言すると:
sub mypush (@@)
sub mypush :prototype(@@)
mypush() は push() が取るのとまったく同じ引数を取ります。
perlsub - Perl のサブルーチン - プロトタイプ
遣付「中途半端に静的型付けのようなマネをするメリットがよくわからないし、
逆に見通しがよくなるわけでもないので
プロトタイプを使うのはやめよう。」
第十条 二項:サブルーチンプロトタイプは使わない。
新人「ふむふむ。
@_
を前提にサブルーチンの引数を処理していくという形で理解しました。
でも**$_
はわかりずらいし使いたくない**ですね…
どうすればよいでしょうか?」
sub sample{
# 見づらい
print $_[0];
print $_[1];
print $_[2];
}
sample(1, 2, 3);
遣付「一般的にはmyを使ってレキシカル変数に入れてしまうのがよいとされているね。
myという演算子を今まで普通に使っていたけど
これはその宣言をしたスコープでしか使えないという意味なんだ。」
my 演算子は、それを囲んでいるブロック、条件文 (if/unless/elsif/else)、ループ (for/foreach/while/until/continue)、サブルーチン、eval、 あるいは do/require/use されたファイルにレキシカルに閉じ込められる 変数を定義します。
perlsub - Perl のサブルーチン - my() によるプライベート変数
新人「なるほどー。」
遣付「また、リスト値のコンストラクターの仕様に準拠すると…」
リスト自身を構成する個々の要素すべてに代入が許される場合にのみ、全体のリストに代入を行なうことができます:
perldata - Perl のデータ型 - リスト値のコンストラクター
my $x;
my $y;
my $z;
# 左辺のリストと右辺のリストの要素数が同じ。
($x, $y, $z) = (1, 2, 3);
# 左辺より右辺の要素が多い場合は切り捨て。
# $xに1が、$yに2が代入されて、3は使われない。
($x, $y) = (1, 2, 3);
# 左辺より右辺の要素が少ない場合は一部の値が代入されない。
# 厳密には、$xには1が、$yには2が、$zには未定義値(undef)が代入される。
($x, $y, $z) = (1, 2);
# 未定義値なので↓はエラーになる。
# print $z;
遣付「こんな風に書けるという事だ。」
sub sample{
# それぞれの引数をレキシカル変数に代入
my ($name, $age, $gender) = @_;
print $name."\n";
print $age."\n";
print $gender."\n";
}
sample('Hogeyama Fugataro', 25, 'gentle man');
新人「ふむふむ。」
遣付「要は要素の個数が合ってりゃいいって事なんで
下のようにも書けるよ。」
sub sample{
# 引数をそのまま配列としてレキシカル変数に代入。
my @param = @_;
print @param;
}
my @lower_alphabet = ('a' .. 'z');
# aからzまでを出力
sample(@lower_alphabet);
sub sample{
# 引数の一番目、二番目をスカラとして代入、残りを配列で代入。
my ($name, $age, @personality) = @_;
# $nameを出力
print $name."\n";
# $ageを出力
print $age."\n";
# $nameと$ageを除いた残りの@_を出力
print join "\n", @personality;
}
sample('Hogeyama Fugataro', 25, 'gentle man', 'handsome', 'friendly', 'high-income');
新人「引数の受けが基本的に配列@_
なので、
配列を渡す時は直感的にわかりやすいですね。
ただハッシュの場合はどうすればよいのでしょう?」
遣付「じゃあ寄り道してハッシュをちょっとだけ詳しくみてみよう。
まずPerlのハッシュ実装はハッシュテーブルというデータ構造に基づいている。」
ハッシュテーブル (英: hash table) は、キーと値の組(エントリと呼ぶ)を複数個格納し、キーに対応する値をすばやく参照するためのデータ構造。ハッシュ表ともいう。ハッシュテーブルは連想配列や集合の効率的な実装のうち1つである。
(Wikipediaより抜粋)
遣付「Javaだと例えばHashMap
、Pythonだと辞書型、などなど
他の言語でも使われている有名なデータ構造だね。」
プログラミング言語におけるハッシュテーブルの実装(Wikipedia)
新人「そうですね。」
遣付「でも思い出してみて欲しい。
Perlのハッシュを初期化する時はどうするのが普通だ?」
新人「えっと、ファットコンマでキーとバリューを繋ぐんでしたね。
あれ?でも、ファットコンマ=>
って普通のコンマ,
と同義だから…
これ…!よくよく見たらリストでハッシュを初期化しています。」
my %hash1 = ('a', 1, 'b', 2, 'c', 3);
my %hash2 = (a=>1, b=>2, c=>3);
遣付「その通り。
配列と同じように、ハッシュもリスト代入ができるんだ。
簡単に言えば()
で括られていて、,
で区切られているものがリストだ。
直感的に勘違いしやすいんだけど、配列=リストではない。
配列はデータ型だが、リストはただの表記方法に過ぎないんだ。
リスト値は、個々の値をコンマで区切って (必要に応じて括弧で括って) 示されます
perldata - - リスト値のコンストラクター
新人「あ。配列とリストを混同していたかもしれないです。お恥ずかしい…」
遣付「つまりは配列もハッシュもリスト代入が可能という事だから
例えばハッシュを配列にしたい場合は
単純に代入すればよいだけだね。」
# ハッシュを宣言して初期化。
my %hash = (a => 1, b => 2, c => 3);
# ハッシュを配列に代入
my @arr = %hash;
# c3a1b2を出力。(ただしハッシュテーブルなので順番は不定)
print @arr;
print "\n";
新人「はぇ~」
遣付「また例えばハッシュをループで回す時には
keys
関数やvalues
関数がよく使われるけど、その仕組みについて
ハッシュテーブルというブラックボックスにアクセスして
キーをごにょごにょしたりバリューをごにょごにょしたりして
特定の値を引っ張ってきてくれている
と無駄に難しく考えるよりかは
ハッシュをリスト値と捉えた上で
keys
は0を含む偶数番目のインデックス値を
values
は奇数番目のインデックス値を
持ってきてくれている
と考えた方がシンプルじゃないかなぁとは思う。
厳密には違うかもだけどね。」
# ハッシュを宣言して初期化
my %hash = (a => 1, b => 2, c => 3);
# ↓と同義
# my %hash = ('a', 1, 'b', 2, 'c', 3);
# keys関数でループ
for my $k (sort keys %hash){
# abcを出力(リスト上のインデックスは0, 2, 4)
print $k;
print "\n";
}
# values関数でループ
for my $v (sort values %hash){
# 123を出力(リスト上のインデックスは1, 3, 5)
print $v;
print "\n";
}
# keys関数を使ってキーとバリューと両方抜く。
for my $k (sort keys %hash){
# それぞれのキーとバリューを出力
print "key: $k, value: $hash{$k}";
print "\n";
}
perlfunc - sort
perlfunc - keys
perlfunc - values
遣付「ちなみに、上のkeys
とvalues
のPerldocを見てもらうとわかる通り
Perlのバージョン5.12以降では、each
関数よろしく
ハッシュだけでなく配列に対してもkeys
とvalues
が使えるから
リスト値の偶数番目のインデックス、奇数番目のインデックスという考え方は
配列に対しては適切ではなくなるけど、まぁこの現場は5.10.1だし。(震え声)」
新人「(また、すねてる…)」
遣付「閑話休題。
ハッシュをサブルーチンの引数として渡したい場合は
もうわかるよね?」
新人「はい!こうですね!」
sub sample{
# 引数をそのままハッシュとしてレキシカル変数に代入。
my %param = @_;
print %param;
}
my %hash = (a=>1, b=>2, c=>3);
# c3a1b2を出力。(ただしハッシュテーブルなので順番は不定)
sample(%hash);
遣付「その通り。つまりここまでの結論としてはこうだ。」
第十条 三項:単純にリスト値として渡せる引数(配列、ハッシュ)はそのままレキシカル変数にリスト代入する。
新人「という事は単純にいかないケースがあるという事ですね?」
遣付「察しがいいな。
例えば、配列を二つ渡したい場合だ。」
sub sample{
my (@param1, @param2) = @_;
print "param1:";
print @param1;
print "\n";
print "param2:";
print @param2;
}
my @arr1 = (1, 2, 3);
my @arr2 = (4, 5, 6);
sample(@arr1, @arr2);
param1:123456
param2:
新人「あれ?param1
に全部入っちゃいました。」
遣付「そう。
呼び出し元では二つの配列に分けているつもりでも
サブルーチンで受け取った時は一つのリスト値として評価されてしまう。
引数は**@_
にリスト値として丸ごと展開されてしまう**から
呼び出し元の引数の切れ目がわかんなくなってしまう、という事だね。」
新人「うーん。どうすればいいでしょう。」
遣付「そこで思い出してほしい。
サブルーチンの引数はスカラのリストである事を。
逆に言えば、スカラとして区別がされているのであれば
呼び出し先でもその切れ目を判断できる、という事だ。
つまり、スカラとして配列を表現できればいいよね。」
新人「なるほど!リファレンスですね。」
遣付「正解!」
これらの呼び出しリストと戻り値リストにある全ての配列とハッシュは、 潰されてそのアイデンティティを失います。 しかし、これを避けるために常にリファレンスで渡すことができます。
perlsub - Perl のサブルーチン - 説明
sub sample{
# スカラを二つ受け取る。
my ($param1, $param2) = @_;
print "param1:";
# デリファレンスする
print @{$param1};
print "\n";
print "param2:";
# デリファレンスする
print @{$param2};
}
my @arr1 = (1, 2, 3);
my @arr2 = (4, 5, 6);
# スカラを二つ渡す。(リファレンスはスカラである。)
sample(\@arr1, \@arr2);
param1:123
param2:456
新人「ふむふむ。
スカラとして区別がついていればよいという事ですね。
じゃあ、例えばこんなのも…」
sub sample{
my ($param1, $param2, %param3) = @_;
print "param1:";
print $param1;
print "\n";
print "param2:";
print $param2;
print "\n";
print "param3:";
print %param3;
}
sample('aiueo', 'kakikukeko', 'A' => 1, 'B' => 2, 'C' => 3);
param1:aiueo
param2:kakikukeko
param3:A1C3B2
新人「…こんなのも」
sub sample{
my ($param1, $param2, $param3) = @_;
print "param1:";
print $param1;
print "\n";
print "param2:";
print @{$param2};
print "\n";
print "param3:";
print %{$param3};
}
my $str = 'aiueo';
my @arr = (1, 2, 3);
my %hash = ('A' => 1, 'B' => 2, 'C' => 3);
sample($str, \@arr, \%hash);
param1:aiueo
param2:123
param3:A1C3B2
新人「…動くということか!」
遣付「いいぞいいぞ。」
第十条 四項:配列やハッシュを複数、引数として渡したい場合はリファレンスを使う。
遣付「あと、補足するなら引数がスカラ変数一つの場合だね。
shift
関数を使うと便利だ。」
shift ARRAY
shift
配列の最初の値を取り出して、その値を返し、配列を一つ短くして、すべての要素を 前へずらします。 配列に要素がなければ、未定義値を返します。 ARRAY を省略すると、サブルーチンやフォーマットのレキシカルスコープでは @_ を、サブルーチンの外側で、eval STRING, BEGIN {}, INIT {}, CHECK {}, UNITCHECK {}, END {} で作成された レキシカルスコープでは @ARGV が用いられます。
perlfunc - shift
sub sample{
my $param = shift @_;
# @_は省略可能。
# my $param = shift;
# リスト代入でも同じ事が行える
# my ($param) = @_;
print $param;
}
sample('aaa');
新人「これはshift
一つだけの方がわかりやすいかもですね。」
遣付「そうだね。
慣れない内はリスト代入my ($param) = @_;
でもいいと思うけど
折角なので標準化しておこうか。」
sub sample{
my $param = shift;
print $param;
}
sample('aaa');
第十条 五項:引数がスカラ変数一つの場合は@_
を省略し、shift
関数で受ける。
遣付「引数についてはこんなもんかな。
じゃあ戻り値にいこう。」
新人「オッケーです!」
第十一条:サブルーチンの戻り値はreturnかdieでキメよう。
遣付「戻り値…一般的には返り値やリターン値とも呼ばれたりするね。
Perldocでは『戻り値』と呼ばれている事が多いので
以降は戻り値と呼称する事にする。」
新人「わかりました。」
遣付「さて、じゃあ大事な大前提を最初にぶち上げておこう。
Perlにおいては基本的に戻り値のないサブルーチンは存在しない。」
新人「えぇぇ!?そうなんですか?」
遣付「あぁ。次を見てくれ。」
sub sample{}
my $ret = sample();
print $ret;
# Use of uninitialized value $ret in printエラーを出力。
新人「$ret
が未定義だと怒られていますね…
sample
サブルーチンが何も返していないからではないんですか?」
遣付「よし。じゃあ$ret
を初期化しておこうか。」
sub sample{}
my $ret = 'ABC';
$ret = sample();
print $ret;
# Use of uninitialized value $ret in printエラーを出力。
新人「んんん?同じですね?」
遣付「わかったろう。
空のsample
サブルーチンは未定義値を返却しているんだ。」
もし何の戻り値も指定しなければ、サブルーチンはリストコンテキストにおいては 空リストを返し、スカラコンテキストにおいては未定義値を返し、 無効コンテキストではなにも返しません。
perlsub - Perl のサブルーチン - 説明
遣付「明示的にも記載されているね。」
空のサブルーチンは空リストを返します。
perlsub - Perl のサブルーチン - 説明
新人「サブルーチンの中に何も書かれていないと
空リスト、つまり未定義値が返るのはわかりました。
では第十条でサンプルプログラムとして扱っていた
print
しかしないサブルーチンは何が返っているのですか?」
sub sample{
my $param = shift;
print $param;
}
sample('aaa');
遣付「うん。じゃあ、やってみようか。」
sub sample{
my $param = shift;
print $param."\n";
}
my $ret = sample('aaa');
print "Content of \$ret is [${ret}].";
aaa
Content of $ret is [1].
新人「1?
1って何ですか?」
遣付「print
関数の戻り値だ。」
新人「print
の戻り値?」
文字列か文字列のリストを出力します。 成功時には真を返します。
perlfunc - print
遣付「そしてこうだ。」
return がなく、最後の文が式だった場合、その値が返されます。 最後の文が foreach や while のようなループ制御構造だった場合、 返される値は不定です。
perlsub - Perl のサブルーチン - 説明
新人「はぁ…最後の式の評価が返っているという事ですか。
これはわかりずらいですね。
ちなみに無効コンテキストというのは何でしょう?」
もし何の戻り値も指定しなければ、サブルーチンはリストコンテキストにおいては 空リストを返し、スカラコンテキストにおいては未定義値を返し、 無効コンテキストではなにも返しません。
perlsub - Perl のサブルーチン - 説明
遣付「無効コンテキストというのは
評価した結果を利用しない、つまり値を期待しないコンテキストだ。
例えばこんな。」
'aiueo';
遣付「サブルーチンを呼び出す時も同様に
戻り値を呼び出し側で受けない形は無効コンテキストとなる。」
subroutine(PARAM);
新人「なるほど。」
遣付「つまり、こういう事だ。」
sub sample(){}
sample();
遣付「この場合戻り値としては何も返さないし
呼び出し元でも何もしないという事だね。
サブルーチンの中身が空でなくても同じだ。
無効コンテキストの場合は何も返さない。」
#!/usr/bin/perl
use strict;
use warnings;
sub sample{
# 無効コンテキスト
# Useless use of a constant in void context警告は出力されない。
# (サブルーチンの中だから)(よくわからんがそういうもんだと思おう。)
'aaa';
}
# 無効コンテキスト
# Useless use of a constant in void context警告を出力。
'bbb';
# 無効コンテキスト
# 何も返されないので何もしない。
sample();
# スカラコンテキスト
# 式評価として'aaa'が返ってくる。
my $ret1 = sample();
print '$ret1 is [';
print $ret1;
print "]\n";
# リストコンテキスト
# 式評価として'aaa'が返ってくる。
my @ret2 = sample();
print '@ret2 is [';
print @ret2;
print "]\n";
Useless use of a constant in void context at C:/perltest/test47.pl line 14.
$ret1 is [aaa]
@ret2 is [aaa]
新人「ふむふむ。…なかなかややこしいですね。」
遣付「そう。
だから値を返さないサブルーチン、
つまり無効コンテキストで実行される事が期待されるサブルーチンを作る場合は
明示的にreturn;
を書くようにしよう。」
sub sample{
my $param = shift;
print $param."\n";
return;
}
my $ret = sample('aaa');
print "Content of \$ret is [${ret}].";
aaa
Use of uninitialized value $ret in concatenation (.) or string at C:/perltest/test41_kai_kai_kai.pl line 12.
Content of $ret is [].
新人「あれ?
return;
を追記しただけなのにエラーが出るようになってしまいましたね。
これは…?」
遣付「perldocを見よう。」
return EXPR
return
サブルーチン, eval, do FILE, sort ブロックまたは正規表現 eval ブロック (但し grep や map ブロックではない) から EXPR で与えられた値をもって、リターンします。 EXPR の評価は、返り値がどのように使われるかによってリスト、スカラ、 無効コンテキストになります; またコンテキストは実行毎に変わります (wantarray を参照してください)。 EXPR が指定されなかった場合は、リストコンテキストでは空リストを、 スカラコンテキストでは未定義値を返します; そして(もちろん) 無効コンテキストでは何も返しません。
perlfunc - return
遣付「引数のないreturn;
は未定義値を返す。
順番を考えれば
『役割と用途をしっかりと建て付けてサブルーチンを作る』
の後に
『サブルーチンを呼び出して使う』
だよね。
元々、return;
で戻り値を建て付けたという事は
このサブルーチンは未定義値を戻り値とする仕様だ。
つまり、戻り値を期待して
my $ret = sample();
という風に呼び出している
この使い方が悪いという事になるよね。」
新人「あー。なるほど。
逆に間違った使い方をすればエラーが起きると。」
遣付「そうだ。
そもそも未定義値が戻る事がわかっているのであれば
わざわざ戻り値を受ける必要はないよね。」
sub sample{
my $param = shift;
print $param."\n";
return;
}
sample('aaa');
aaa
新人「確かに。これなら呼び出し方も一目瞭然ですし
サブルーチンを読んだ後に未定義値に触ってしまうリスクも少なそうです。
第十一条 一項:戻り値のないサブルーチンはreturn;
を最低限必ず書く。
第十一条 二項:return;
でしか戻さないサブルーチンの戻り値をレキシカル変数で受けない。
遣付「じゃあ今度は戻り値を返すreturn EXPR;
について考えてみよう。」
sub sample{
return 100;
}
my $ret = sample();
print $ret;
新人「はい。スカラの100が戻ってきてprint…これは単純ですね。」
遣付「じゃあ配列とハッシュはどうだろうか。」
sub sample1{
my %sample1_ret = (hoge => 1, fuga => 2);
return %sample1_ret;
}
sub sample2{
my @sample2_ret = (1, 2, 3, 4);
return @sample2_ret;
}
my %ret1 = sample1();
print %ret1;
print "\n";
my @ret2 = sample2();
print @ret2;
print "\n";
新人「これも問題なさそうですね。」
遣付「…なんだけど、配列とハッシュはできるだけリファレンスで返した方がよい。」
新人「どうしてですか?」
遣付「サブルーチンの引数はリストが基本というのと同じように
サブルーチンの戻り値はスカラが基本としておいた方がシンプルだからだね。」
sub sample1{
my %sample1_ret = (hoge => 1, fuga => 2);
return \%sample1_ret;
}
sub sample2{
my @sample2_ret = (1, 2, 3, 4);
return \@sample2_ret;
}
my $ret1 = sample1();
print %{$ret1};
print "\n";
my $ret2 = sample2();
print @{$ret2};
print "\n";
遣付「あとは、例えばどうしても配列とハッシュを複数戻したい場合は
リスト代入でリファレンスを複数受け取る事もできる。」
sub sample{
my %sample1_ret = (hoge => 1, fuga => 2);
my @sample2_ret = (1, 2, 3, 4);
return (\%sample1_ret, \@sample2_ret);
}
my ($ret1, $ret2) = sample();
print %{$ret1};
print "\n";
print @{$ret2};
print "\n";
新人「そうか。
別にリスト代入は引数を受け取る為だけのものではないですもんね。」
遣付「複数の戻り値が必ずしもスマートかというと微妙だけどね。」
新人「スカラとして何が戻ってくるかは意識しておく必要がありますが
できるだけスカラの形がシンプルという事は理解しました。」
第十一条 三項:サブルーチンの戻り値は(できるだけ)スカラで返す。
第十一条 四項:やむを得ずリストの戻り値を定義したい場合は、スカラのリストとする。
遣付「ここで第九条をもう少し深堀してみよう。」
第九条:サブルーチンの役割と用途は事前に打ち出そう。
遣付「注目してほしいのは役割とreturn
文の関係だ。
直前でやった通り
戻り値がある場合はreturn EXPR;
で返すよね。」
sub sample{
return 100;
}
my $ret = sample();
print $ret;
新人「はい。そうですね。」
遣付「その前段としてそもそも**return;
とreturn EXPR;
のどちらで建て付けるか?**
という決めが大事なんだ。」
新人「どういう事でしょうか?」
遣付「以下の要件を考えてみよう。
演習なのでサブルーチンを作る前提で進めるものとする。」
<要件>
1.ハッシュAに固定値が入ったハッシュBの内容を追加したい。
ハッシュAの内容:キーがランダムで何が入っているかはわからない。
ハッシュBの内容:foo
、bar
、baz
の3つのキーバリューを保持している。
2.ただし、ハッシュAにfoo
、bar
、baz
のキーのいずれかが既に存在する場合
既存以外のキー値、バリュー値のみをハッシュAに追加する。
3.ハッシュAにfoo
、bar
、baz
のキーの全てが存在する場合
全てのキーを上書きする形でハッシュAのバリュー値を更新する。
ハッシュAのfoo | ハッシュAのbar | ハッシュAのbaz | 挙動 |
---|---|---|---|
存在しない | 存在しない | 存在しない | ハッシュBの内容をハッシュAに全て追加 |
存在しない | 存在しない | 存在する | fooとbarのみをハッシュAに追加 |
存在しない | 存在する | 存在しない | fooとbazのみをハッシュAに追加 |
存在しない | 存在する | 存在する | fooのみをハッシュAに追加 |
存在する | 存在しない | 存在しない | barとbazのみをハッシュAに追加 |
存在する | 存在しない | 存在する | barのみをハッシュAに追加 |
存在する | 存在する | 存在しない | bazのみをハッシュAに追加 |
存在する | 存在する | 存在する | ハッシュBの内容でハッシュAを全て更新 |
新人「ぐぬぬ…なかなか厄介ですね。とりあえずやってみます。」
カタカタカタカタカタカタカタカタカタカタカタカタカタカタ
新人「これでどうでしょう!」
# ハッシュB
my %hash_b = (foo => 100, bar => 200, baz => 300);
# 追加する対象を返却するサブルーチン
sub get_additive_for_hash_a{
my $ref_hash_a = shift;
my %ret = ();
my $cnt = 0;
for my $key (keys %hash_b){
if(exists ${$ref_hash_a}{$key}){
$cnt++;
}
}
if($cnt == 0 || $cnt == 3){
%ret = %hash_b;
}else{
for my $key (keys %hash_b){
if(! exists ${$ref_hash_a}{$key}){
$ret{$key} = $hash_b{$key};
}
}
}
return \%ret;
}
# 8パターンのハッシュA
my %hash_a1 = (aiueo => 1, kakikukeko => 2);
my %hash_a2 = (aiueo => 1, kakikukeko => 2, baz => 3);
my %hash_a3 = (aiueo => 1, kakikukeko => 2, bar => 3);
my %hash_a4 = (aiueo => 1, kakikukeko => 2, bar => 3, baz => 4);
my %hash_a5 = (aiueo => 1, kakikukeko => 2, foo => 3);
my %hash_a6 = (aiueo => 1, kakikukeko => 2, foo => 3, baz => 4);
my %hash_a7 = (aiueo => 1, kakikukeko => 2, foo => 3, bar => 4);
my %hash_a8 = (aiueo => 1, kakikukeko => 2, foo => 3, bar => 4, baz => 5);
my @test_data = ();
push @test_data, \%hash_a1;
push @test_data, \%hash_a2;
push @test_data, \%hash_a3;
push @test_data, \%hash_a4;
push @test_data, \%hash_a5;
push @test_data, \%hash_a6;
push @test_data, \%hash_a7;
push @test_data, \%hash_a8;
# メイン処理
for my $ref_hash (@test_data){
my $additive = get_additive_for_hash_a($ref_hash);
my %hash_a = (%{$ref_hash}, %{$additive});
print %hash_a;
print "\n";
}
遣付「なるほど。では作ったサブルーチンの役割を説明してくれ。」
新人「えっと、ハッシュAを引数として受け取って、
ハッシュBのキーと突合し、
ハッシュAに追加するべきハッシュのリファレンスを戻り値とする
サブルーチンです。」
遣付「ふむふむ。
要件だけ満たす事を考えるのであれば
こういう風にも書けるだろう?」
カタカタカタカタカタカタ…ッターン!
# ハッシュB
my %hash_b = (foo => 100, bar => 200, baz => 300);
# ハッシュAに加工を施すサブルーチン
sub modify_hash_a{
my $ref_hash_a = shift;
my $cnt = 0;
for my $key (keys %hash_b){
if(exists $ref_hash_a->{$key}){
$cnt++;
}
}
if($cnt == 0 || $cnt == 3){
%{$ref_hash_a} = (%{$ref_hash_a}, %hash_b);
}else{
for my $key (keys %hash_b){
if(! exists $ref_hash_a->{$key}){
$ref_hash_a->{$key} = $hash_b{$key};
}
}
}
return;
}
# 8パターンのハッシュA
my %hash_a1 = (aiueo => 1, kakikukeko => 2);
my %hash_a2 = (aiueo => 1, kakikukeko => 2, baz => 3);
my %hash_a3 = (aiueo => 1, kakikukeko => 2, bar => 3);
my %hash_a4 = (aiueo => 1, kakikukeko => 2, bar => 3, baz => 4);
my %hash_a5 = (aiueo => 1, kakikukeko => 2, foo => 3);
my %hash_a6 = (aiueo => 1, kakikukeko => 2, foo => 3, baz => 4);
my %hash_a7 = (aiueo => 1, kakikukeko => 2, foo => 3, bar => 4);
my %hash_a8 = (aiueo => 1, kakikukeko => 2, foo => 3, bar => 4, baz => 5);
my @test_data = ();
push @test_data, \%hash_a1;
push @test_data, \%hash_a2;
push @test_data, \%hash_a3;
push @test_data, \%hash_a4;
push @test_data, \%hash_a5;
push @test_data, \%hash_a6;
push @test_data, \%hash_a7;
push @test_data, \%hash_a8;
# メイン処理
for my $ref_hash (@test_data){
modify_hash_a($ref_hash);
print %{$ref_hash};
print "\n";
}
遣付「このサブルーチンの役割は
ハッシュAを引数として受け取って、
ハッシュBのキーと突合するまでは同じだが、
ハッシュAにダイレクトに更新をかけるというサブルーチンだ。
元値を変えてしまうので戻り値は不要となりreturn;
となる。
もしくは、サブルーチン処理自体の成否を示す
リターンコードをreturn EXPR;
で返してもいいかもね。」
新人「なるほど!
これでも要件を満たせますね!」
遣付「例えばだが、
順序を入れ替えたい加工用サブルーチンの作成がいくつか想定されていたり、
もしくは、順序不問の加工要件の死活をサブルーチン呼び出しの有無で制御したり、
といった用途を前提にアルゴリズムを建て付けたい場合は有効に働くね。
ただ、元値を更新してしまうという大きな作用があるので
取扱いには十分注意しなくてはいけない。」
新人「ふむふむ。」
遣付「このように、
要件によっては**return;
とreturn EXPR;
のどちらが適切なのか?**
というのが変わってくるんだ。
サブルーチンの役割と用途というのは
最初に建て付けた後はできる限り変えない方が望ましい。」
新人「確かに。戻り値の扱いも全然変わっちゃいますしね。」
遣付「そう。上の例では戻り値の様式が変わるだけだが
役割と用途がぶれると引数の扱いも変わりかねない。
だから役割と用途を事前に打ち出すのが大事なんだ。」
新人「承知しました!」
第十一条 五項:サブルーチン作成後に引数と戻り値の様式が変わる事のないよう、事前に打ち出した役割と用途に対して適切なサブルーチンの作用を意識してサブルーチンの初期構築を行う。
遣付「作用の話が出たので@_
の補足をしておこう。
@_
というよりかは$_
を使わない方がいい理由だね。」
sub sample{
$_[0] = 5;
}
my $param = 2;
sample($param);
print $param;
5
新人「うわ!元値が変わっちゃいました。」
遣付「そうなんだ。@_
をレキシカル変数に代入する事無く
ダイレクトに代入をしてしまうとそのまま反映されてしまうんだ。
配列 @_ は local 配列ですが、その要素は実際の スカラパラメータの別名です。 たとえば $_[0] が更新された場合、対応する引数が更新されます (更新できない場合にはエラーとなります)。
perlsub - Perl のサブルーチン - 説明
新人「これは予期せぬ作用になりかねないからダメですね。」
遣付「まぁ第十条でも謳っているし、あくまで補足という事で。
よし。じゃあreturn
については理解が得られたと思うので
die
の説明をしていこう。」
新人「そういやreturn
かdie
でキメようという題目でしたね。
お願いします。」
遣付「コンパイルが通っていれば動くような単純なサブルーチンであればよいが
例えば、DBにコネクションを貼る、Webサービスを呼び出してJSONを得る、
前に出たファイルオープンの話もそうだね。
実行時エラーが想定されるサブルーチンというのも普通に考えられる。」
新人「そうですね。」
遣付「そういう場合は正常時のreturn
とは別にdie
を書いておくのが無難だ。」
sub sample{
my $sample_ret = 0;
my $error = 0;
#
# (処理中略)
#
# 例外時
if($error){
die "Error occured."
}
return $sample_ret;
}
#
# (処理中略)
#
my $ret = 0;
eval{
$ret = sample();
};
if($@){
# 例外処理
}
遣付「$@
にはエラーメッセージが入る。
慣れない内はeval
ブロック~if($@)
までを
イディオムとして捉えておけばいいだろう。」
構文エラーや実行エラーが発生するか、die 文が実行されると、 eval はスカラコンテキストでは undef が、 リストコンテキストでは空リストが設定され、 $@ にエラーメッセージが設定されます。 (5.16 以前では、バグによって、リストコンテキストで構文エラーの時には undef を返していましたが、実行エラーの時には 返していませんでした。) エラーがなければ、$@ は空文字列に設定されます。
perlfunc - eval
新人「了解です!」
第十一条 六項:実行時エラーが想定されるサブルーチンであればreturn
とは別にdie
を記述する。(合わせて、呼び出し元ではeval
ブロックを適用しdie
での戻り値がないか評価する事。)
遣付「よし。じゃあ締めに向かおう。」
第十二条:サブルーチンのI/F定義は(最低限)コメントを必ず残そう。
遣付「本来であればサブルーチンの
InputとOutputを整理した設計書を用意するのがよいのだが…」
新人「そんな時間はないですね…」
遣付「であれば最低限コメントは残すようにしよう。
例えば君がさっき作ったサブルーチンだな。」
# 追加する対象を返却するサブルーチン
sub get_additive_for_hash_a{
my $ref_hash_a = shift;
#
# (処理中略)
#
return \%ret;
}
遣付「最初と最後だけ見れば
何か引数を一つ受け取って
ハッシュのリファレンスをreturn
しているのはわかるが
肝心の、サブルーチンが中で何をしているか?がわからない。
return
が最後だけとは限らないし
途中でdie
しているかもしれない。」
新人「確かに。」
遣付「なので、最低限必要なのは以下の通りだ。」
- 引数(型、数)
- 戻り値(型、数)
- サブルーチンの中で何をしているかの概要
- 実行時エラーケース(どういう場合に
die
するか)
新人「なるほど。コメントの体裁は気にした方がいいですか?」
遣付「うん。サブルーチンのコメントの書き方として
標準化されている形がベストだが最初はそこまで気にしなくてもいいだろう。
自分の建て付けた役割と用途を謳う事、つまり
第三者に全てのソースコードを読ませないようにするのが目的だからね。
後になって、自分の建て付けを振り返れるという点も大きい。」
新人「ふむふむ。やってみます!」
# 追加する対象を返却するサブルーチン
#
# 引数:ハッシュAのリファレンス
# 戻り値:ハッシュAに追加する対象のハッシュリファレンス。
# 概要:ハッシュAとハッシュBを突合し、要件に従いハッシュAに追加するべきハッシュを生成する。
# 実行時エラー:特になし
sub get_additive_for_hash_a{
my $ref_hash_a = shift;
#
# (処理中略)
#
return \%ret;
}
遣付「どうだ?
これだけの情報でもあるとないとでは変わるだろう?」
新人「はい!」
遣付「後は必要に応じてサブルーチンのコメントの書き方も揃えていくと
プロジェクト全体のソースコードがグッと読み易くなると思うよ。」
新人「わかりました!」
遣付「いよいよ最後だ。」
第十三条:無闇に小難しい概念を取り入れるのはやめよう。(やるなら慎重に)
遣付「ネットの情報を鵜呑みにしたらダメだ。」
新人「どうしたんですか?いきなり。」
遣付「いや。はやる気持ちを抑えきれなくてな。」
新人「はぁ。」
遣付「これまでに様々な標準化を施してきた。全部振り返ってみよう。」
第一条:まず「記号」に悩んだらPerldocを見よう。
第一条 一項:記号で困ったらとりあえずPerldocを見る。
第一条 二項:スクリプトを書く際はstrict
プラグマとwarnings
プラグマを必ず使用する。
use strict;
use warnings;
第二条:演算子にまつわる書き方を統一しよう。
第二条 一項:論理否定演算子!
の後にはスペースを一つ置く。
第二条 二項:論理否定演算子not
は使わない。
第二条 三項:ハッシュのキー・バリューはファットコンマ(=>)でつなぐ。(初期化時)
第三条:構文糖(糖衣構文)はほどほどに。使うなら統一しよう。
第三条 一項(改訂):ハッシュの初期化子において、キーはクォートしない。キーを動的にしたい場合はハッシュを初期化したあとに行う
第三条 二項:アロー演算子やデリファレンス修飾子は(できるだけ)省略しないで書く。
第四条:制御文(条件分岐)はifもしくはunlessとorだけでいい。
第四条 一項:条件分岐には基本的にif文を使用する。
第四条 二項:unless文は文修飾子でのみ使用する。
第四条 三項:or文はファイルのopenイディオムでのみ使用する。
第四条 四項:条件演算子(三項演算子)はもし使うならシンプルに。
第五条:制御文(ループ)はforかwhileでいい。
第五条 一項:foreachは使わない。
第五条 二項:for文のVARは省略しないで必ずmyをつける。
第五条 三項:untilは使わない。
第五条 四項:do whileは使わない。
第五条 五項:while eachイディオムは使わない。(改訂の余地あり)
第五条 六項:ループを実現する際はfor文かwhile文を使用する。(冗長記載だけど明記しておく)
第五条 七項:無限ループを実装する場合は、ループ終了条件が収束するように確実に設計する。(具体策は別途、考えよう)
第五条 八項:ループ制御文はnextとlastのみを使用し、redoとcontinueは使わない。
第六条:文字列の取り扱いを決めよう。
第六条 一項:文字列変数のみを引用する場合は変数展開${}
を使用する。
第六条 二項:文字列変数に加えて関数を引用する場合は変数展開と合わせて、文字列連結演算子.
を使用する。
第六条 三項:変数展開を行う際に、見通しがよくなるのであれば冗長であっても${}
を使う。
第六条 四項:ハッシュの代入において、キー値に変数展開を用いてもよいが、見づらくなるようであれば、キー値用のスカラ変数を別途用意する。
第六条 五項:クォート風演算子は使わない。(暫定)
第七条:変数名は確実に命名しよう。
第七条 一項:同じ変数名を使わない。
第七条 二項:一文字の変数は使わない。
第八条:関数とサブルーチンの棲み分けをしておこう。
第八条 一項:自分で作成した関数を『サブルーチン』と呼称する。
第八条 二項:perlfuncで規定されるAPI関数、およびモジュールないしサードパーティ製のライブラリで提供される関数を『関数』と呼称する。
第九条:サブルーチンの役割と用途は事前に打ち出そう。
第九条 一項:既に存在するサブルーチンは作らない。
第九条 二項:既に存在する関数ないしサブルーチンであっても、役割と用途を改善・拡張する形であれば、あらたにサブルーチンを作ってもよい。
第十条:サブルーチンの引数はわかりやすくそしてシンプルに。
第十条 一項:サブルーチンの呼び出し時はカッコを付ける。subroutine(PARAM)
第十条 二項:サブルーチンプロトタイプは使わない。
第十条 三項:単純にリスト値として渡せる引数(配列、ハッシュ)はそのままレキシカル変数にリスト代入する。
第十条 四項:配列やハッシュを複数、引数として渡したい場合はリファレンスを使う。
第十条 五項:引数がスカラ変数一つの場合は@_
を省略し、shift
関数で受ける。
第十一条:サブルーチンの戻り値はreturnかdieでキメよう。
第十一条 一項:戻り値のないサブルーチンはreturn;
を最低限必ず書く。
第十一条 二項:return;
でしか戻さないサブルーチンの戻り値をレキシカル変数で受けない。
第十一条 三項:サブルーチンの戻り値は(できるだけ)スカラで返す。
第十一条 四項:やむを得ずリストの戻り値を定義したい場合は、スカラのリストとする。
第十一条 五項:サブルーチン作成後に引数と戻り値の様式が変わる事のないよう、事前に打ち出した役割と用途に対して適切なサブルーチンの作用を意識してサブルーチンの初期構築を行う。
第十一条 六項:実行時エラーが想定されるサブルーチンであればreturn
とは別にdie
を記述する。(合わせて、呼び出し元ではeval
ブロックを適用しdie
での戻り値がないか評価する事。)
第十二条:サブルーチンのI/F定義は(最低限)コメントを必ず残そう。
第十三条:無闇に小難しい概念を取り入れるのはやめよう。(やるなら慎重に)
新人「うわぁ。書くも書いたり、という感じですね。」
遣付「だが、これで終わりじゃないぞ。
例えば第五条で出てきたwhile~each
イディオムや
第六条で出てきたクォート風演算子や単語リスト演算子など
標準化を先送りにしたものもあるしね。」
新人「そうですね。」
遣付「なので最後の条では新規に標準化として建て付ける際の注意点を
挙げておこう。
まぁ、題目通りだけどね。」
新人「下手にしゃらくさい事はすんなという
強いメッセージ性だけは感じます。」
遣付「なかなか勘がいいな。
じゃあもういいか。」
新人「いえ。
もうだいぶお疲れでしょうが
簡単に説明をお願いします。」
遣付「わかった。がんばろう。
Perl言語の初出はWikipediaによると
はるか昔1987年の事だ。
Perl5だと1994年だね。
そんな長い歴史が一言で語られるべくもなく
Perlの文献はとても多い。
書物もさることながらネット上の文献もだね。
で、現場でよく目にするのは
どちらかというとネットの文献ではないかな。」
新人「そうですね。
本も一応手元には置いていますが
作業の合間だと手軽なので
ついついネットを参照する事の方が多いです。」
遣付「そこで冒頭の言葉だ。
ネットの文献を鵜呑みにしてはいけない。
記事によってはPerlのバージョンが明記されていなかったり
やたらマニアックなものをこねくり回したり…と。」
新人「あー。何となくわかります。」
遣付「ただ勘違いしないで欲しい。
全ての文献は著者にとっては間違いなく有用なものだ。
決して下手に考えなしにdisってはいけない。
大事な事はただ一つの客観的な事実。
『他人の文献で謳われている利便性はその人にとってのものであり
この現場にとって有用とは限らない』という事実だ。」
新人「なるほど。
じゃあPerldocを見れば確実って事ですね!」
遣付「いや、Perldocも鵜呑みにしてはダメだ。」
新人「えぇ!?」
遣付「便利そうだなぁと思って
よくよくPerldocを読んだら
『はい。これは実験的なAPIでした~。』と
残念でした~と言わんばかりの記述も中にはある。
無論、実験的なAPIや未成熟なAPIが全てダメなわけではない。
が、実験台にされるリスクと商用バグは天秤で計れないものだろう。」
新人「まぁ、そうですけど…
じゃあ、わかりました!
ここは書籍でs」
遣付「Noだ。」
新人「食い気味!」
遣付「例えば有名なPerl本として
Perlベストプラクティスという本がある。
でもベストじゃなくない?と言っている人もいる。
まぁこれは個人の価値観によるかもだけど。
ただ、さっきも言った通りPerlの歴史は長い。
その時代時代で最善とされてきた慣習が今ベストであるという確証はないんだ。」
新人「じゃあ、どうすれば…」
遣付「答えは簡単。
自分で手を動かして試してみる事さ。」
新人「手を動かす…」
遣付「そう。
『これは便利だよ!』と謳っているTipsを鵜呑みにして
いずれ商用として運用されるかもしれないソースコードに
いきなりTipsの切れ端を書き始めるのは愚策だ。
標準化の観点からしてもよろしくない。」
新人「標準化されていないものは書いてはいけないという事ですね。」
遣付「それだと語弊があるかもしれない。
標準化は確かにルールではあるが、
例外を絶対に認めないという事ではない。
標準化に縛られ過ぎてどう書けばよいのかわからなくなるのは本末転倒だし
後追いで標準化していくというケースも当然、出てくるだろう。」
新人「ふむふむ。」
遣付「問題はそう、Tipsのコピペという点なんだ。
それだと既存ソースからのコピペと何も変わらなくなってしまう。
だから良さげな文献を見つけて
自分のソースコード構築にもメリットがありそうなのであれば
きちんと検証ソースを別に用意して、
自分で手を動かして、自分の目で良し悪しを判断しろという事だ。」
新人「なるほど!
それが現場にとって有用とは限らないというポイントですね!」
遣付「その通り。
現場に従事している人間が標準化を建て付ける事に意味があるんだ。
逆に言えば、現場にいない人間が標準化を建て付けたとしても
それはただの理想論で終わってしまう可能性もある。
何が有用で、何を不要とするのか、
それは現場の文化にも左右されるし
ユーザによっても、納期によっても、
はたまたジョインしているエンジニア達のスキルセットによっても
変わってくるだろう。」
新人「ですねー。
でも、有用かどうかって乱暴に言うと
さじ加減だと思うんですが
どういう風にその境目を見極めていけばよいですかね?」
遣付「そうだね。
難しいところだけど…。
例えば、これまでロクに触れていないPerlの大きな武器である正規表現だが、
この先必ず標準化が必要になるだろう。
これくらいならまだいい。
既存ソースでも正規表現はたくさん使っているだろうから
振り返りや過去の知見のマージという形でも標準化が可能だ。」
新人「はい。イケそうです。」
遣付「ただ、ここの既存ソースコードで1ミリも使われていないような概念、
例えばオブジェクト指向プログラミング(OOP)とかだね。
こいつを『あぁ便利そうだ。早速やってみよう。』と
気軽に標準化するのは果たして効率的なのか?という疑問が残る。」
新人「うーん。確かに無理に使うのは厳しいかもですね。」
遣付「あとは名前空間であるパッケージを始めとした
シンボルテーブルや型グロブなども説明していないな。
ただ、この先は標準化のメリットが大きくなってくるかもしれない。
これ以外にもまだまだ有用に働くかもしれないものは色々あるぞ。」
新人「ふむふむ。
とりあえず情報を集める事自体は悪い事ではないと判断しました。
有用かどうかを自分で試して判断する為にも。」
遣付「アンテナ張りすぎてもアレなので、あとは時間と相談だね。」
新人「ですねー。」
遣付「…ところで、どうかな?
ここまでだけでも得られた事はPerlに特化した内容だろうか?」
新人「いえ。考えを改められた気がします。」
遣付「Tipsを自分で検証する事ができた、そこから標準化を建て付けられた。
もうその知見だけで武器なんだ。
武器の種類と数を増やすことも、時には捨てることもできる。
そして存分にその武器を振るう事に何のお咎めがあろうか。
当初の君がなりたいイメージに少しでも近づけたと思うが如何かな?」
新人「はい!ありがとうございます!」
遣付「よかったよかった。
ふぅ。なんだかんだで一日費やしてしまったな。
明日からガンガンいこう!」
新人「よーし!やるぞー!」
冒険の終わりに
~~小粋なBGMが流れる、とあるBAR~~
陰照「はぁ…そんな事があったんですね…」
遣付「あぁ。」
新人「でもちゃんとバージョン1532はやり切りましたよ!陰照先輩!」
陰照「それは何よりです。
それにしても標準化ですか…考えた事もなかったですね。」
遣付「まぁ詳しくは現場に戻ってから標準化仕様書を見てみてくれ。
書き殴りもいい所だが、なかなかのモノに仕上がったぞ。」
新人「自分も加筆してます!フフン!(得意げ)
ともあれ、リリースも無事に終わった!
陰照先輩も無事に退院できた!
めでたい!
乾杯しましょう!カンパイ!」
チーン
遣付「何回カンパイするんだよ(笑)」
陰照「いやはや…その節はお二方には大変迷惑をかけてしまい…」
遣付「お前も何回謝るんだよ…そんな奴にはこうだ!」
ペタッ
陰照「うわっ!何するんですか?」
新人「出た!遣付先輩のおでこに付箋攻撃だ!」
陰照「…何ですか?コレは。」
新人「覚えが悪いとおでこに標準化の内容だったり
ありがたいお言葉が書かれた付箋を貼られるんです。」
陰照「えぇ…何その風習…コワい」
新人「私も一時期、貼られ過ぎてのれんみたいになってました!」
陰照「それはやり過ぎかと。」
遣付「まぁ半分冗談だ。
陰照も何かあった時はそれを見てくれ。
励みになればいいのだが。」
陰照「はい…ってアレ?店のBGMが急に止みましたね。」
遣付「…っと、そろそろか。」
新人「そうみたいですね。名残惜しいですが。」
陰照「え?まだ、飲み始めたばかりじゃないですか。」
遣付「陰照、がんばれよ。」
新人「陰照先輩!また会いましょう!」
陰照「え?え?どういう事…」
(暗転)
エピローグ
???「…陰照君…陰照君!?」
陰照「(zzz)ハッ…はい!」
???「進捗会議中に居眠りとはいい度胸だねぇチミィ…!」
リーダー「申し訳ございません!!マネージャー殿!
陰照は…その…申し上げにくいのですが
20連勤中、かつ3徹目でして…」
Mgr「20連勤~?3徹~?
そんな言い訳が通るわけないだろう!軟弱な!」
リーダー「ははぁ!申し訳ございません!申し訳ございません!」
(ペコペコ)
(ガミガミ)
Mgr「それに何だね!
この陰照という男が課題管理表に挙げた内容は!
チミィ!読み上げてみたまえ!」
リーダー「えっと…Perlコードとして何をどう書けば正解なのかがわからないですね…」
Mgr「素人かね!」
会議参加者A(荒くれもののPerler)
「オイオイ!冗談は程々にしとけよ!
そんなんでPerl開発ができると思ってんのかよ!オォン?」
会議参加者B(モヒカンのPerler)
「ヒャッハー!そんなんじゃロクにコンパイルも通さねぇぜぇ…?」
会議参加者C(トゲトゲ肩パッドのPerler)
「汚物は消毒だ~~~!」
会議参加者D(バギーに乗ったPerler)
「土下座するなら許してやってもいいぜぇ!」
リーダー「申し訳ございません!申し訳ございません!
幾分まだPerlを始めたてなものですから…!
何とか…何とか挽回させますんで!」
(ザワザワ)
(ガヤガヤ)
陰照「(そうか…僕は進捗会議中に居眠りをしてしまったのか…)」
陰照「(何がPerlを愛し、Perlに愛された男だ。
僕は何て恥ずかしい白昼夢を…)」
Mgr「えぇい!リーダー!
貴様の白々しい釈明はもういい!」
リーダー「申し訳ございません!申し訳ご…ガハァッ!(吐血)」
Mgr「陰照とかいったな、この居眠り男が!
居眠りだけじゃなく、なんだ?
そのおでこに貼っ付けているのは?!
ふざけているのかね?!」
(ヒラヒラ)
陰照「(これは…遣付先輩の…?)」
Almost nothing in Perl serves a single purpose.
(PerlのAPIはほぼ例外なく複数の目的をかなえる。)
Larry Wall. 1997
陰照「(遣付先輩…!!)」
Mgr「チミィ!聞いているのかね!?
この不始末、進捗の遅れ、どうしてくれるのかね!
どうやって取り戻すというのだね!?」
(バァン!!)
一同「ビクゥッッッ!!!」
Mgr「な、何だ!?
急に机を叩いて立ち上がって…!
言いたい事があるなら言ってみたまえ!!」
陰照「…私が」
リーダー「陰照君…」
陰照「いえ、我々が最優先でやらなければいけない事。それは…」
一同「(ゴクリ)」
陰照「標準化です。」
~~Fin~~
おわりに
著者がPerlで最初に作ったのは例に漏れずCGIでした。
今でいう調整さんのようなWebシステム、
それをDBの代わりにCSVファイルを使ったり
無駄に正規表現を駆使したり
無駄に独自ドメインを取得したりして
キャッキャウフフしながら一人で悦に入っておりました。
無論、プライベートな遊びで、です。
その頃は言語仕様?知るか。動きゃいいんだろボケがという感じで
Perlと戯れていましたが、それから幾星霜を経て
まさか現場でPerlを触る事になるとは夢にも思いませんでした。
悪い意味で。
Perlの書き方なんか覚えているはずもありませんし
ちゃんと勉強しておけばよかったと思ったのは言うまでもありません。
Perlを作ってて楽しかったのは確か。
その思いだけを頼りに現場でPerlに取り組みましたが
**アゴか何かでタイピングしたのかな?**と思うような
既存のPerlプログラムが及ぼす精神汚染は筆舌に尽くしがたく
心を折られるのはそう遅くはありませんでした。
逆に言えば、汚いソースだったからこそ
腹を括ってPerlをもう一度勉強するきっかけになった
と思えば必要悪だったのかな?とも思いますし
いや、やっぱりそのアゴ割ってやろうか?とも思います。
勉強にはなったので結果オーライです。(ニッコリ)
言語の流行り廃りはあまり関係なく
超レガシーな言語であっても
オワコンと後ろ指を指される言語であっても
そこから得られるものは少なからず必ずある
と筆者は思っていますが
それを時間の無駄と思うかどうかは個人の考え方次第です。
ただネットの情報に左右されずに
自分の目と指で確認していって欲しいとは思います。
長文乱文、失礼致しました。
ここまでご覧頂きありがとうございました。
(Appendix1) Stack Overflow Developer Survey 2020
【2020/05/30追記】
脚注でも触れていますが、Stack Overflow Developer Survey 2020において
Perl言語が高給ランキングのトップに入ったようです。またまたご冗談を!
厳密には給与の中央値のランキングなので
どこまで現実に即した数字なのかは微妙です。
が、調査結果の信憑性を差し引いても、なかなかに感慨深いですね。
あのPerl君がねぇ…大したもんだ。(偉そう)
リンクはこちら。
原文
Globally, respondents who use Perl, Scala, and Go tend to have the highest salaries
, with a median salary around $75k.
Interestingly, Perl is amongst the top most dreaded languages
, so it's possible that this high salary is to compensate for the dearth of developers who want to use that technology.
When looking only at the US, Scala developers tend to have the highest salaries.
翻訳
世界的に見て、Perl、Scala、Goを使用している回答者の給与は最も高い傾向にあり、
給与の中央値は約75kドル(2020/05/29時点のレートでおよそ809万円)です。
興味深いことに、Perlは最も嫌われている言語のトップに入っているので、
この高い給与は、その技術を使いたいと思う開発者の不足を補っている可能性があります。
アメリカだけを見ると、Scala の開発者の給料が最も高い傾向にあります。
そんなハッキリ言うなよ…(落涙)
でも、もしこのランキングがある程度の的を射た事実だとするなら
華々しい超最先端の技術と同様、技術者数の絶対的な不足、
要は「やれる人がいない」という状況が「高給」に繋がっているわけで
色々と思う所はありますね。
COBOLかな?
(Appendix2) オンラインで学べて高い報酬が得られるプログラミング言語トップ15
【2020/06/16追記】
Business Insiderさんの記事「オンラインで学べて高い報酬が得られるプログラミング言語トップ15」にて
Perlがトップでした。
世界では何が起きているの…?
リンクはこちら。
YAHOO!JAPANニュースにも載ったようです。
(Appendix3) Perl7が満を持して登場
【2020/07/07追記】
Perlが四半世紀ぶりのメジャーバージョンアップを発表しました。
ただ、発表しただけなので、実際のPerl7リリースはまだまだ先です。
RC版が出るまで早くとも半年、
できれば来年2021年中に7.0としてユーザリリースしたいとの目論見のようです。
簡単なまとめを置いておきます。
Perl7
現在の最新バージョンPerl5.32をベースとした新バージョンとなります。
Perl5.32ベースと言いつつ、ニュアンスとしては「ほぼ一緒」との事です。
Perl 7 is v5.32 with different settings. Your code should work if it’s not a mess.
(意訳:Perl7は異なる設定を伴うだけのPerl5.32。めんどいPerl5コードでなければPerl7でもほぼ動くよ。)
既存のPerl5対応CPANモジュールは基本的に動作する方向で進めているそうです。
また、Perl5からの移行性はそこまで低くはないとの事です。
use compat::perl5; # act like Perl 5's defaults
ただ、肝心のPerl7としての目新しい機能はほぼないみたいです。(えっ)
Q.What’s appearing?
A.Not much.
(意訳:何か新しいのある?いや、ねぇな。)
Perl言語の基盤として新たにPerl7(≒Perl5.32)を拡張していく指針なんだとは思いますが
現時点では、Perl5からPerl7への移行のメリットを
現実的に大きく打ち出すのは難しいかなぁとも思うので
Perl7のロードマップがもっと明確に宣言されて
各現場における既存の課題・問題を解決するに十分な根拠を見いだせるのであれば
移行するメリットは十分ありそうです。
果たして、Perlが再び覇権を取る日は訪れるのか…?
Perl6
Perl6はPerl5に対する後方互換性を持たないので
Perl5とはまったく別の言語と言ってもよいでしょう。
Perl6は2015年に正式版がリリースされた割と新しめのPerlバージョニングです。
が、後方互換性をウリにしてきたPerl(Perl5)なのだから
このPerl6というバージョニングはおかしいのでは
とかねてより議論が繰り広げられていたようです。
そこで2019年の10月に「Raku」という名称に変更され
Perl6のサイトドメインも「perl6.org」から「raku.org」へと変わりました。
このRakuという命名はおそらく
Perl6コンパイラの「Rakudo」に由来するものと思われますが
Rakudoの公式サイトもぶっ飛んでいてなかなか面白いです。
"Rakudo" is short for "Rakuda-dō" (with a long 'o'; 駱駝道), which is Japanese for "Way of the Camel".
"Rakudo" (with a short 'o'; 楽土) also means "paradise" in Japanese.
(意訳:RakudoはRakuda-dōの略なんだ。日本語で言うと「ラクダの道」だね。
また、オーを伸ばさないRakudo(楽土)は日本で「楽園」を意味するね。HAHAHA。)
※Rakudo公式のトップページにある鳥居の画像の上で数秒マウスオーバーすると、
上記のイースターエッグが表示されます。
Perl5
Perl7が出たらPerl5はお払い箱なのか…とお嘆きの皆さん。
安心してください。
Perl5は長期保守を目的としたLTM(Long Term Maintenance)モードに突入します。
What’s happening to Perl 5?
No one is taking Perl 5 away from you;
it goes into long term maintenance mode—a lot longer than the two years of rolling support
for the two latest user versions.
That might be up to a decade from now (or half the time Perl 5 has already been around).
訳)
Perl5はどうなっちゃうの?
誰もあなたからPerl 5を奪うつもりはありません。
Perl5は、2つの最新のユーザバージョンの
2年間のローリングサポートよりもずっと長い長期メンテナンスモード(LTM)に入ります。
このLTMは今から10年続くかもしれませんし、
あるいはPerl5がすでに存在している時間の半分くらいは続いてしまうかもね!
Perl5が出て四半世紀。
まだまだPerl5は元気です。
あと10年はイケます。
-
Stack OverflowのDeveloper Survey 2020(2020/02)にて、Perl言語はなんと高給ランキングのトップに輝きました!Business Insiderさんの記事「オンラインで学べて高い報酬が得られるプログラミング言語トップ15」(2020/06/16)でもトップの模様。稿末のAppendixに該当記事へのリンクと本文を載せています。 ↩