第1幕: 検分 ── 二階へ上がる
二階へ上がる階段は、段ごとに鳴る音の高さが違う。下から三段目は低く、七段目は乾いた高い音がして、最後の一段だけはほとんど鳴らない。三十年も上り下りしていると、目をつぶっていても、今どこまで上がったかが足の裏で分かる。
午後だった。表の通りの声は一階の土間で止まって、二階までは上がってこない。格子窓から斜めに入った光が、畳の縁に細長い形を落としていた。
二階の奥は勘定場になっている。店主が月末に帳尻を合わせる小さな座敷で、古い文机の横に端末が一台置いてある。大福帳――売り買いを日ごとに書き足していく帳面――は、今はもう紙ではなく、その端末の中のファイルに一行ずつ増えていく。ふだん二階に上がるのは、私と店主くらいのものだ。
「店主から、頼まれごとがありまして」
階段を上がりきったところで、私は振り返って言った。後ろからクロベさんとナギさんが、道具箱を提げて上がってくる。
「二階の記帳の書き方を、一度検めてほしい、と。戻り金のときは金額の頭に三角を付ける決まりなんですが、先月、それが一件だけ違って見えた気がするそうで。気がする、というだけで、どの日のどの行かまでは――」
言いながら勘定場へ目を向けると、クロベさんはもう文机の前に膝をついて、端末の画面を見ていた。いつ私を追い越したのか分からなかった。階段の途中から、開けたままの襖越しに画面が見えていたのだと、あとになって気づいた。
画面には、二階の記帳を受け持つ仕組みのソースが開かれていた。
ナギさんは道具箱を下ろすと、勘定場の手前の床板に手をかけた。
「一枚、上げてもいいですか」
「どうぞ」
答えるのに迷わなかった。二階の床板を人に上げさせたことは、これまで一度も無い。ナギさんがバールの先を差し込むと、古い釘が短く鳴いて板が浮いた。床下の暗がりから、何年分かの埃の匂いが立ちのぼる。その下に、太い梁が一本、横たわっていた。
ナギさんが拳の背で梁を叩いた。詰まった、重い音がした。
「ここは、柱にしっかり噛んでいます。組み上げたときのまま、どこも浮いていません」
クロベさんは画面に目を置いたまま、短くうなずいた。それから、その一部を目で追いながら、声に出して読みはじめた。
| |
「newで組み立てて、postで一行書く。それだけの仕組みです」
クロベさんは、postのほうから先に説明した。日付と品目と金額を受け取り、タブで区切った一行にして書き足す。金額は三桁ごとにカンマで区切る。数字をいったん逆さに並べ、三文字ずつに切ってカンマで繋ぎ、もう一度逆さに戻す――そういうやり方で区切っているのだという。負の金額なら、頭に三角を付けて、数字のほうは絶対値で書く。品目の中にタブが紛れ込んでいたら、空白一つに置き換える。
「このタブの置き換えは、何のためなんですか」
「大福帳は、タブで欄を分けています。品目の中にタブが入ったまま書くと、一行の欄の数が狂って、あとで読むときに品目と金額がずれます。この一行があるから、今まで壊れた行が大福帳に残らずに済んでいる」
それから、クロベさんはnewのほうへ目を移した。
「こっちが、今日の本題です」
open my $fh, '>>', $args{path}という一行だけは、クロベさんの読む速さが落ちた。
「openはファイルを開く命令です。'>>'は、追記で開く、という指定です。ファイルが無ければ作り、あれば末尾から書き足す。前の日の記帳を上書きせずに、毎日少しずつ伸ばしていけるのは、この一行のおかげです」
開いたファイルを扱う手――ファイルハンドル――は、my $fhという入れ物に収められ、そのまま組み立てたものの中に仕舞い込まれる。以後、postはその手を使って書き込む。
「つまり、これは組み立てた途端に、本物の大福帳を開きます」
「検めをしたら、本物の大福帳に一行入ってしまう、ということですか」
私が訊くと、クロベさんは一言だけ答えた。
「入ります」
床下から、ナギさんが梁を叩く音がもう一度した。
第2幕: 手が入らない ── 名前が無い
ナギさんが床板を脇に立てかけ、手の埃を払ってクロベさんの横へ来た。画面を覗き込み、それから顔を上げた。
「じゃあ、この前みたいに――」
「localは届きません」
クロベさんの声が、ナギさんの言葉の終わりに重なった。
「この前の、子のクラスで上書きする手も、ここでは使えません」
ナギさんは口を開いたまま、少しのあいだ動かなかった。それから、「……はい」とだけ言った。膝の上に置いた手が、そのまま止まっていた。何か二つ、言うつもりだったのだと思う。その二つとも、先に言われてしまったのだ。
クロベさんは、画面のほうを向いたまま、一つずつ話しはじめた。
「まず、localです。localは、名前の付いた入れ物の中身を、しばらくのあいだだけ別のものにすり替えて、終わったら元に戻す仕組みでした。店の間の値札のときに使った手です」
私は、あのときのことを思い出していた。税率を返す仕組みの名前を、検めのあいだだけ別のものに差し替えた。差し替えたのは、パッケージに登録された名前――誰からでもShop::Tax::rateのように呼べる名前――だった。
「ところが、このmy $fhは、そういう名前の付いた入れ物ではありません。myで作った入れ物は、それを書いた場所の中――ここではnewの中――だけにあって、パッケージの名前の一覧には載りません。newの外から見ると、この入れ物には、名前が無い」
「名前が無い」
私が繰り返すと、クロベさんは顎を引いた。
「名前が無いものは、localで指しようがありません」
ナギさんが、ゆっくりと顔を上げた。
「パッケージの名前を付けて書いたら、どうなりますか。$Nikai::Daifuku::fhみたいに」
言いかけて飲み込んだものの一つが、それだったのだろう。クロベさんは、それを待っていたように答えた。
「書けます。Perlはそれを黙って通します。そして、本物の大福帳に書きます」
クロベさんは、端末の脇に短いコードを打ってみせた。
| |
「$Nikai::Daifuku::fhというのは、Nikai::Daifukuというパッケージの$fhという名前の入れ物です。そういう入れ物は、書けばその場でできてしまう。できたばかりの空の入れ物を、localは言われたとおりすり替えます。でも、newの中のmy $fhとは、まったく別のものです。だから、すり替えても何も起きない」
「何も起きない、というのは……」
「エラーにはなりません。used only once: possible typo――この名前は一度しか使われていないが、打ち間違いではないか――という警告が一本出るだけです。同じ名前をどこかでもう一度書けば、その警告さえ消えます。検めのほうは本物の大福帳に一行書いて、何事も無かったように終わる」
クロベさんは、そこで一度言葉を切った。
「一番怖いのは、これです。止まらずに、間違ったほうへ書く」
私は、店主の顔を思い浮かべた。月末に大福帳を開いて、見覚えの無い畳表の売り上げが一行入っているのを見つける。誰が書いたのか分からない一行を、店主はきっと一晩かけて探すだろう。
「井戸のときの手のほうは、どうですか」
ナギさんが訊いた。これが、もう一つだったのだと思う。
井戸の一件のとき、クロベさんたちは、外へ問い合わせに行く呼び出しを、その仕組み自身のメソッドとして切り出した。検めのときだけ、そのメソッドを上書きした子のクラス――サブクラス――を使う。そうして、外へ出る縄だけを、検め用の縄に替えたのだった。桶をつけない縄――あのときクロベさんは、そう呼んでいた。
「あれは、上書きできるメソッドがあって、初めて効く手です」
クロベさんは、newの中のopenを目で指した。
「openは、Perlに組み込みの命令です。この仕組みのメソッドではありません。子のクラスでpostを上書きしても、組み立てるときには親のnewが動いて、openはそのまま走ります。上書きできる継ぎ目が、ここには無いんです」
「組み込みの命令そのものを、差し替える手は無いんですか」
ナギさんが食い下がった。
「あるにはあります。CORE::GLOBAL::openという名前に仕込めば、openを横取りできる。ただ、呼ぶ側のコードが読み込まれる前に仕込まないと効きませんし、効いたら効いたで、その間に走るすべてのopenを巻き込みます。二階の大福帳だけを狙って替える、ということができません」
「newごと上書きしたら」
「できます。ただ、そのためにはnewの中身をまるごと子のクラスへ書き写すことになる。そうすると、検めは本物のnewを一度も通らなくなります。本物の組み立て方が変わっても、検めは気づかない。何を検めているのか分からなくなります」
ナギさんが、自分の手を見下ろした。何かを書く前の手の形のまま、指が止まっていた。
私は、ナギさんが上げた床板の下を覗いた。梁は、組み上げたときのまま、柱に深く噛んでいた。どこにも隙間は無く、仮の支えを横から差し込めるような余地は、見当たらなかった。
「一つだけ、書いてみてください」
クロベさんが、ナギさんに言った。
「証文代わりです。$fhに、そのままlocalを掛けてください」
ナギさんは検めのファイルを開き、一行だけ書き足した。
| |
走らせると、画面に二行の文字が出た。
| |
$fhという名の入れ物は、どこにも無い。パッケージの名前を付けて書け。――検めは、一行も動かずに止まっていた。
誰も、何も言わなかった。
第3幕: 手立て ── 柱を外から持ち込める仕口
最初に口を開いたのは、クロベさんだった。
「親切に、my $fhを宣言し忘れていないか、と訊いてくれていますが」
画面の末尾の括弧を、クロベさんは目で示した。
「言われたとおり、ここでmy $fhと書いても、それは検めのファイルの中に新しくできる、別の入れ物です。大福帳のnewの中の$fhには、どうやっても届きません」
ナギさんが、書き足した一行を消した。
「では、どうするんですか」
私が訊くと、クロベさんは、画面をnewのほうへ戻した。
「中で作っているから、外から届かない。だったら、外から渡せるようにします」
クロベさんは、手を膝に置いて、正面から私のほうを見た。
「コンストラクタのパラメータ化――Parameterize Constructor――という技法です。コンストラクタ、つまりこのnewのような組み立て役の中で作っていた相手を、引数で受け取れるようにする。受け取らなかったときは、今までどおり自分で作る」
「受け取らなかったときは、今までどおり」
「そこが肝心です」
クロベさんは、postのほうを目で示した。
「postは、受け取った手にprintするだけです。だから渡すものは、書き込めるファイルハンドルでありさえすれば、何でも構いません。渡す側が請け合うのは、その一点だけです」
クロベさんはうなずいた。
「この技法を書いた人も、そこを大事にしています。引数を一つ足すと、今この仕組みを呼んでいる所が全部、新しい引数を渡さなくてはならなくなる――そう思い込んで、みんなこの手を思いつかないのだ、と。実際には、元の呼び方を残したままにしておける。書いた人が例に使っていた言語では、組み立て役を二つ並べて、引数の無いほうから、引数のあるほうへ、いつもの相手を作って渡すように書きます」
「Perlでも、二つ並べるんですか」
「Perlのnewは、名前付きの引数を一つの束で受け取ります。二つ並べる代わりに、一つのnewの中で分かれ道を作れば、同じことになります」
クロベさんがナギさんのほうを見た。ナギさんは、もう画面の前に座り直していた。
「ledgerという名前で、渡せるようにしてください。渡されなかったら、今までどおりpathを開く」
ナギさんが打ちはじめた。my $fh = $args{ledger} ||まで書いて、手が止まった。
「どっちでも動きますよね。||でも、//でも」
ナギさんは、画面を見たまま訊いた。クロベさんが答えた。
「今日のところは、どちらでも動きます。ただ、//にしてください」
「違いは何ですか」
私も訊いた。クロベさんは、画面の二つの記号を目で比べてから話した。
「||は、左側が偽なら右側を使います。Perlでは、未定義の値――undef――だけでなく、0や空の文字列も偽です。//のほうは、左側が未定義のときだけ右側を使います。値が渡されていれば、それが0でも空でも、そのまま使う」
「ファイルハンドルは、0になることはありませんよね」
ナギさんが言った。
「なりません。開いたファイルハンドルは、いつも真です。だから今日は||でも同じに動く。それでも//にするのは、渡されたものを、偽に見えるからといって勝手に捨てない、という書き方の癖を揃えておくためです。癖が揃っていれば、次に誰かが数を渡す引数を足したときに、0を黙って捨てずに済みます」
ナギさんは||を消して、//と打ち直した。//はPerl 5.10から入った記号で、今のPerlなら何も気にせず使えるのだと、クロベさんは付け加えた。
書き上がったnewは、こうなった。
| |
変わったのはnewの中の、大福帳を開く所だけで、postには一文字も触れていない。
「do { ... }は、中の何行かを順に実行して、最後の行の値をその場の値にする書き方です。ここでは、開いたファイルハンドル$realが、その値になります」
クロベさんは、//の右側を目で示した。
「大事なのは、//の右側は、左側が未定義のときにしか実行されない、ということです。ledgerを渡せば、doの中身は動かない。openが走らない。本物の大福帳は、開かれもしません」
私は、床下の梁のほうを見た。
「梁を組むときに、柱を外から持ち込めるように、仕口を開けておくようなものです」
クロベさんが言った。
「持ち込めば、その柱で組む。持ち込まなければ、いつもの柱で組みます」
「柱ごと、外から渡すんですか」
思わず、そう訊いていた。クロベさんは、少しだけ口元を緩めた。
「渡したければ。渡さなければ、何も変わりません」
組み立てるときに何を使うかを、組み立て役の中で決めてしまうのではなく、組み立てを頼む側が選べるようにする。これまでnewの中に固く噛んでいた本物の大福帳との結びつきが、呼ぶ側の手元へ移ったのだと、私は理解した。
「では、今この仕組みを使っている所は、何か変えないといけないんですか」
引き受ける側として、それを一番に確かめておきたかった。クロベさんは、店主が月末に使っている締めの手順を開いた。
| |
「Nikai::Daifuku->new(path => $DAIFUKU_PATH)。今日の手を入れる前も、入れた後も、この一行は一文字も変わりません。ledgerを渡していないので、newは今までどおりpathを開きます」
「じゃあ、検め用の何かを渡すのは、誰なんですか」
「検めのコードです」
クロベさんは、はっきり言った。
「検め用の帳面を作ってledgerで渡すのは、検めのコード自身です。検めのときだけ、検めのコードが組み立て役に柱を手渡している。組み立てているのは、どちらの場合も同じnewです」
私は野帳に、組み立ての前と後で、誰が大福帳を決めているのかを並べて描いてみた。

描いてみると、変わったのはnewの入口の一か所だけで、店主の手順からpostまでの線は前と同じ場所を通っていた。
「検め用の帳面、というのは」
「これです」
クロベさんは、さっき一度見たのと同じ一行を指した。
| |
「\my $bufは、my $bufという変数をその場で作って、\でその参照を渡す書き方です。openは、ファイルの名前の代わりに、こうして変数への参照を渡すと、ファイルではなくその変数に向かって開きます。書き込んだ中身は、ファイルではなく$bufという変数に溜まる。手元で読める、紙一枚の帳面のようなものです。中身を確かめるのは、closeで閉じてからにします。閉じる前だと、書いたものがまだ手前で溜まっていて、変数に届いていないことがあるので」
ナギさんが、何か言いたそうに私のほうを見て、それからクロベさんに訊いた。
「桶をつけない縄とは、何が違うんでしょう。どっちも、検めのときだけ別のものを使う、ですよね」
「あのときは、仕組みの中に継ぎ目になるメソッドを作って、それを子のクラスで上書きしました。今日は、継ぎ目のメソッドも、子のクラスも要りません」
ナギさんは、画面のnewをしばらく見ていた。
「……今日のほうが、組み立てるところが、よく見えます」
第4幕: 検め ── 仮の部材を入れ替える手
検めは三つに分かれていた。
一つ目は、今までの呼び方で組み立てたとき、手を入れる前と後のnewがまったく同じ大福帳を書くことの確かめだった。
| |
検めのためだけに、手を入れる前のnewをNikai::Daifuku、手を入れた後をNikai::DaifukuAfterという別の名前で並べてある。ファイルを写してパッケージの名前だけを替え、postは一字も違えずにそのまま、違うのはnewだけだ。検めが済めば、手を入れた後の形を本体へ戻す。どちらもpathだけを渡す、今までどおりの呼び方で組み立てる。正の金額、負の金額、ゼロ、カンマの付かない三桁の数、七桁の数、タブの紛れ込んだ品目。並べた入力を両方に同じ順で書かせ、書き込まれた二つのファイルの中身を、先頭から末尾まで比べる。tempdirは、検めが終われば消える一時的な置き場所を用意してくれる道具で、本物の大福帳には近づかない。
「今までの呼び方で組んだら、前と同じ大福帳が、同じように伸びます」
クロベさんはそれだけ言った。
二つ目は、検め用の帳面を渡したときの確かめだった。
| |
帳面に入った二行が、決まりどおりの形をしていること。三桁区切りのカンマ、戻り金の三角。そして、pathに大福帳の置き場所を渡しているのに、そこにファイルが作られていないこと。ledgerを渡したので、openは走らなかったのだ。
三つ目は、ledgerを渡さないとき――正確には、渡しても中身が未定義のとき――に、今までどおりpathのファイルへ書き込まれることの確かめだった。//は未定義の値を「渡されていない」と同じに扱うので、そこも押さえておくのだという。
走らせると、どれも通った。
| |
画面を見ていて、一つ気になることがあった。
「localのときは、検めが終われば元に戻る仕組みがあったから、安心して使えました。今日のは、何を頼りに安心していいんでしょう。戻し忘れたら、誰が気づくんですか」
呼ぶ側が選べる形になったことは、分かったつもりでいた。けれど、選んだあとの後始末を誰がするのかは、まだ聞いていなかった。
クロベさんは、答えなかった。代わりに、画面の検めのコードの、ledger => $memと書いてある一行を目で示した。
私はその一行を読んだ。検めの中で、Nikai::DaifukuAfter->newを一つ組み立てている。その一つにだけ、検め用の帳面を渡している。
どこかの名前を、すり替えているわけではなかった。店主の締めの手順が呼ぶnewも、二階の勘定場の大福帳も、どこも何も替わっていない。検めの中で組み立てた一つが、検めの中で使われて、検めが終わるとそのまま無くなる。
「戻すものが、無いんですね」
口にしてから、自分で少し驚いた。クロベさんは、ようやくうなずいた。
「はい。どこも替えていないので、戻し忘れようがありません」
私は、端末のほうへ手を伸ばしていた。
誰かに促されたわけではない。キーボードの上で少し迷ってから、検めのコードのopen my $memの行を真似て、その下にもう一行、open my $mem2, '>>', \my $buf2 or die $!;と書いた。そして、ledger => $memの$memを、$mem2に書き換えた。閉じる行のclose $memもclose $mem2に、帳面の中身を受ける$bufも$buf2に直した。キーの一つ一つを確かめながら打ったので、ずいぶん時間がかかった。
走らせると、通った。
横で、ナギさんが膝の上の手をほどくのが見えた。
「帳面を替えても、組み立て方も、postも、何も変わらずに通る」
クロベさんが言った。
「検め用の部材を入れ替えるのは、渡す一行を書き換えるだけです。今日の技法で開けたのは、そのための差し込み口です」
それから、クロベさんは、保証することと、しないことを区切って話した。
「今日の手で言えるのは、記帳の書き方を、本物の大福帳に触れずに検められるようになった、ということです。今この仕組みを呼んでいる所は、何も変えなくていい。手を入れる前と後で、今までの呼び方なら、どんな金額でも同じ行が同じファイルに書かれる」
「言えないのは」
「本物の大福帳が開けないとき――置き場所が無い、書き込みが許されていない、置いてある所がいっぱいになった――そういうときの振る舞いは、検めていません。今までどおりの経路は、一時的な置き場所で一度通しただけです。それに、これは一度動かしたときの話です。毎日この勘定場で使い続けていて、何が起きても大丈夫だ、とまでは言えません」
クロベさんは、床下の梁のほうへ一度目をやった。
「もう一つ。渡せるようにしても、この仕組みは、本物の大福帳の開き方をまだ自分の中に抱えています。渡されなかったときのために、です。渡すものが二つ、三つと増えていけば、newは膨らみます。渡されなかったら自分で作る、という形は、今呼んでいる所を守るための既定であって、これで仕上がりというわけではありません」
「渡されなかったら自分で作る、というのは、よくないやり方なんですか」
「よそでは、嫌われることもあります。組み立てを自動でやってくれる道具を使う言葉だと、その道具が、引数の無い呼び方のほうを選んでしまって、せっかく用意した検め用のものが渡らない、ということが起きる。この店のPerlには、そういう道具は入っていませんから、その心配は今はありません。ただ、自分で作る分だけ、本物とのつながりはまだ中に残っている――それは覚えておいてください」
私は、野帳を開く前に、一度、端末に向き直った。
検めの中ではなく、店主がいつも開いている本物の大福帳を開いた。一番下までずっと送っていく。最後の行は、昨日の夕方、店主が書き足した釘の仕入れのままだった。畳表の一万二千も、三角の付いた返品も、どこにも入っていなかった。
それを確かめてから、私は野帳に書いた。
「仮の部材を、自分で入れ替えられる」
書いたあとで、野帳の余白に、さっき自分で打った一行を小さく書き写した。ledger => $mem2。明日の朝には、たぶんもう、何も見ずに打てる。
振り返ると、ナギさんが床板を元に戻していた。木槌で二度、軽く叩く。板は元の場所に収まって、梁はもう見えなくなった。格子窓の光は、さっきより少しだけ長く伸びて、畳の縁を越えていた。
普請控
- 見立て: 二階の勘定場で記帳を受け持つ
Nikai::Daifukuは、組み立てた途端に本物の大福帳を開いていた。開いたファイルハンドルはnewの中のmy $fhに収まっていて、外からは名前が無い。localは届かず、パッケージ名を付けて書けば黙って空振りして本物に書き、子のクラスで上書きしようにもopenには上書きできる継ぎ目が無かった - 手を入れた所: コンストラクタのパラメータ化(Parameterize Constructor)を使った。
newに省略できるledgerを足し、渡されなければ今までどおり自分で大福帳を開くように、$args{ledger} // do { open ... }と書いた。検めでは、変数に向かって開いた検め用の帳面を渡す - 触っていない所:
postの中身は一文字も変えていない。タブで欄を分ける形、三桁区切り、戻り金の三角という記帳の決まりもそのまま。店主の締めの手順が呼ぶNikai::Daifuku->new(path => ...)という呼び方も、大福帳ファイルの置き場所も、末尾から書き足していく開き方も変えていない - 次の工程へ送ること: 店主が気にしていた、先月一件だけ三角の書き方が違って見えたという件は、今日は探していない。本物に触れずに検める手立てができたので、次はそれを使って先月分を確かめる。本物の大福帳が開けないときの振る舞いも、まだ検めていない
- 私が引き受けたこと: 検め用の帳面を、今日は自分の手で別のものに差し替えて通した。仮の部材を入れ替えるのは渡す一行を書き換えるだけで、どこも替えていないから戻し忘れも起きない。だから、これからは検め用の部材の入れ替えを、棟梁を待たずに自分でやる
