Help us understand the problem. What is going on with this article?

第3話: 俺のコードがこんなに破壊的なわけがない

More than 5 years have passed since last update.

前回

←第2話

月曜昼の使者

 僕たちは、部室で昼休みの弁当を食べていた。イリスは相変わらずドーナッツを食べている。

 「あのさ、ドーナッツばかりで栄養が偏らない?」
 「常に同じものが入れば、同じ結果になるの。それが安心するの」

 それは常人の言葉でいうと、偏食っていうんじゃないのかな。そして、部長のほうを見てみると、なぜか弁当箱に『計算論 計算可能性とラムダ計算』が入っている。それ、明らかに昼食じゃないよね……。

 そんなほのぼの(?)とした昼食だった筈なんだけど、突然の乱入者が現れた。

 「もしもし、この近所に同一なものはありませんか」

 その男子は、背の高く、すらりとした美少年であった。ただ顔色が青褪めていて、神経質そうだった。

 「あなたに義侠心というものがあるなら、ぼくを同一性へ案内してください……」

 「いやそもそも、同一性というのが……」

 すると、突然、蓮家が勢いよく部室に飛び込んできた。口には焼きそばパンとメロンパンが咥えられている。そして、その美少年を指差して、こう断言した。

 「モゴモゴ……きみは、こう言いたいのでしょう!関数型はどこだ!」
 「悪質な冗談はやめてください、プロセスが死ぬかもしれないのですよ」

 うーん、このやり取り、何処かで見たことがある気がするんだけど、こういう小ネタみたいなのがちょこちょこ入るのはどうなんだろう。

 「とりあえず、話は聞かせてもらうわ。こっちも焼きそばパンを確保するのに忙しかったし。売り切れなんていう状態管理なんて面倒くさくてありゃしない。並列性ってものが考えられないわけ」

 だからそのコンピューター用語で会話するのやめろって。しかも言ってる当の本人、よくわかってないだろ。

同一性を訪ねる少年、理由を聞く

 昼食を食べ終わった間、授業が始まるまで、僕たちはその美少年の話を聞いていた。

「あの……僕はプログラミングを書いていたんですけど、メメメバグがいるとは思わなくて……たまたま僕は簡単なブログっぽいプログラムを書いてたんですけど、メメメバグにコードをかぶりとやられてしまったんです」

「メメメバグってなんなの……」
「端的に言えば、意図しない挙動のことです」
「それは単にバグっていえばいいのに……」

 なんだかややこしい奴だ。

「例えば、僕はこういうコードを書いていたんです:

class Neji
  def initialize(source)
    @source = source
  end

  def palindorome?
    reverse_source = @source
    reverse_source.reverse!
    @source == reverse_source
  end
end

meisha = "kurage"
meme = Neji.new(meisha)
puts meme.palindorome?
puts meisha

 これは、meishaという変数に入っている文字列が上から読んでも、下から読んでも同じ言葉になるかどうかを調べるコードです。もし、「下から読んでも、上から読んでも同じ」であるならば、文字列を裏返しても同じになるはずです。したがって、僕はreverse!というメソッドを使って、元の文字列と同じようになるかどうかを調べてみたんですが、これだと、常に「true」が帰ってきちゃうんです」

 ソースを読んでみた限り、上ので問題はなさそうだけど。

「んー、一読した感じだと、つまり@sourceを一度reverse_sourceにコピーして、それを裏返して、元々の文章と比較するロジックを書きたかったという感じなわけだよね」

「はい、その理解で合ってます。なのに、この結果……あげく、meishaまで壊されてしまう始末……だから、僕は同一というのはなんだ、何が同じなんだ、何が違うんだって悩んでいるわけです」

 部長が弁当箱から顔を上げると、例によって何かの一節を朗読し始めた。

実はわれわれのプログラムがモデル化している実世界の「同じ」の意味もあまりはっきりしない。一般に二つの見かけ上同じなオブジェクトが本当に「同じもの」であるかは、一つのオブジェクトを変えてみて、もう一つのオブジェクトが同じように変わっているかをみて決める。しかし「同じ」オブジェクトを二度観測し、オブジェクト下のある性質が一回目と二回目の観測で違っているということ以外に、あるオブジェクトが「変った」ことがどうして分るだろうか。つまり何が先見的な「同一」という概念なしに「変化」を決めることはできず、変化の効果を観測することなしに同一性を決めることは出来ない。

 そういうと、部長は弁当箱を残して、出て行った。弁当箱を覗いてみると、本のページのかすがたくさんくっついている。部長、まさか……あれ、食べたの……。

「あ、イリス、今ドラは鳴らすなよ、まだ昼休みなんだからな」

イリスは不満そうに、ドラを鳴らす棒を部室のロッカーにしまった。

破壊的な、あまりにも破壊的な

 退屈な古典の授業。

 古典の授業は、老教師がやっているのだけれど、いまいち何を言っているかわからず、結果として「ありをりはべりいまそがり」という呪文を覚えるくらいのことしかわからない、という残念な授業だ。周囲を見ても、真面目に聞いている人間はおらず、人間の最大の敵である「睡魔」と戦うか、人間の最大の生産性を吸い取るスマートフォンをいじっているくらいしかない。

 僕はさっきのコードと、部長の言っていたことを考えていた。

 Rubyによれば、reversereverse!の役割は違うということはよく知られている。前者は、簡単にいってしまえば、その変数の結果自体は変わらない。reverse!は変数の内容自体を変えてしまう。

haisha = "kurage"
haisha.reverse
puts haisha

meisha = "kurage"
meisha.reverse!
puts meisha

 確かに、前者は変数自体は変わらない。しかし、結果を変えたものを保存するためには、別の変数を使わなきゃいけない。すると、変数が多くて見通しが悪い。下の方が、同じ変数を使うわけだから、余計なことをしなくて済む。

 これはプログラミングにとっては明らかに些細なことだ。些細すぎるといってもいい。でも、しかし、何か引っかかる。部長の言葉も意味深だが、ちょっとわからない。

 そんなことを考えていると、後ろから紙切れが当たる。後ろを振り向くと、蓮家がこっちを睨んでいる。俺はそんな悪いことをしたっけ、と思いながら紙切れを拾い上げて広げると何かが書いてある。

『あのね、変えてみないと同一かわからないってことは、変えてみたら同一だった、ということもありうるってことなの。変わらないところと変わるところを熟考しなさい。例えば”2113"という文字列を逆にして数字にして、元の”2113”よりも大きいか、っていうコードを考えて見るの』

 うーん、どういうことだ?

 たぶん、蓮家が言いたいのは、下のような問題に直せる。

数字だけで構成されている文字列が渡される関数がある。この文字列を逆にしたときに、元の数字とどちらのほうが大きいかを比べ、逆にしたものが大きければ真を返すコードを書きなさい。

”2113”なら、逆にすれば”3112”になるため、逆にしたほうが大きいので真である。

ちなみに、同値の場合は不定であり、どちらの結果を返してもよいとする。

 これはたぶん簡単だ。Rubyには文字列を整数値にするto_iという便利なメソッドがある。で、たぶんこの引数をreverseにすればいい。だから、こうなると思う。

def reverse_big?(str)
  reverse_int = str.reverse.to_i
  reverse_int > str.to_i
end

puts reverse_big? "1234"
puts reverse_big? "4321"

 そうすると、こんどは頭に鈍い痛みが走った。どうやらまた蓮家が紙を投げてきたらしい。紙を開いてみると、小石が包まれていて、そこには0点と書かれていた。なんだよ、お前なんのエスパーだよ……。あと小石は投げるな、危険すぎる。

 以前に0点と言われたことを思い出した。要するに余計な変数が入り込んでいるのが問題だ。reverse_intは実質str.reverse.to_iなんだ。だから

def reverse_big?(str)
  str.reverse.to_i > str.to_i
end

 心なしか、こっちのほうがわかりやすい気がした。何しろstrstr.reverseが同じソースを利用して、それを加工した結果が明確になっている気がする。同一と変化。

 「あ、そうか。なるほど」

 僕は思わず叫び越えを出した。老教師はこちらを振り向くと、「服紗君、じゃあこの問題の答えを言ってください」と一言。周囲の生徒はクスクス笑っている。

 後ろを振り向くと、蓮家が呆れた顔をしていた。

メメメバグの正体

 僕は急いで部室に向かう。既に例の美少年は密やかに椅子に座っていた。微動だにせず、ただディスプレイを眺めているようだ。その光景は異質だが、絵画のようでもある。

 「わかった、そのメメメバグ? とかいうやつの正体が」

 美少年はこちらを振り向く。前よりも青ざめた顔をしているような気がする。大丈夫か、病院行かなくていいの?

「要するにこういうことだよ、このコードは余計な変更が入ってしまっているために、違うと思っていた値同士が同じであったために、その変更が波及してしまっているんだよ。だから、さっきのコードは

class Neji
  def initialize(source)
    @source = source
  end

  def palindorome?
    @source.reverse == @source
  end
end

meisha = "kurage"
meme = Neji.new(meisha)
puts meme.palindorome?
puts meisha

haisha = "オレオ"
meme = Neji.new(haisha)
puts meme.palindorome?
puts haisha

 にするといいいんだ。つまり、余計な変更は、余計な変更を波及させてしまう可能性が高くて、そこが考慮になかったのが、前のコードの原因なんだ」

 美少年は喜びと困惑の二つの表情を浮かべた。確かに、彼にすればたぶん、これは意図した挙動なのだろうけれど、でもよくわからないという感じだ。

「あの、正直に聞いていいですか。なんで@source.reverse == @sourceで、動くんですか」

 前の僕はこれを説明できなかったけど、今なら、たぶん、できる。

「まず @source"kurage"に置き換えられる。そのあとにreverseが行われ"egaruk"に変えられる。で、左側の@source"kurage"にが置き換えられる。つまり、結果として

# @sourceが"kurage"の場合:

@source.reverse == @source
-> "kurage".reverse == @source
-> "egaruk" == @source
-> "egaruk" == "kurage"

という風になるんだ。でも、reverse!@source自体の値を変更しちゃうから、別のところにまで問題が波及しちゃうんだ。たぶん、これは元々入ってきたsourceと一緒のオブジェクトだから、なんだと思うけど……大切なのは、元は出来るだけ変更しないほうがよくて、どうやったら既存の値を使いまわして、できるだけ操作が返してくる値を利用するすることができるか、って考えたほうが良かったんだ」

「なるほど……そうだったんだ。ああ、ここに、ここに同一があったのですね」

「んー、その言い方はわからないけれど……まあとにかく、そのメメメバグというのは解決したわけだよね」

「素晴らしい……この部活、このような人物がいるなんて……もしよろしければ入部をお願いしたいんですが……」

 そう言い終わるやいなや、口の中で紙をモグモグしている部長が奥から現れた。やっぱり、それ食事だったんだ……っていうかヤギ?

 部長は黙って指をさす。するとそこには「古谷 井妙田(ふるや いみょうた)」というのがぶら下がっている。

「既にこの部員の一員として認められていたんだ、良かった、お兄さん、これからもよろしくお願いします」

「ちょ、ちょっとまって、お兄さんってなんだよ」

「僕のメメメバグを直してくれたんですから、先輩では足りない親愛を感じます。だから、僕は貴方のことをお兄さんと呼びたいのです」

 もー、なんでこんなややこしい人ばかり集まってくるんだよ、この部活。

 隅にはいつのまにか蓮家が立っていた。そして、こう呟いた。

「ーー5点

顧問の補習授業

あ、私は現代文を担当している「今歩 多香(こんぷ たか)」ね。『副作用部』の顧問よ。愛称はマシーンと呼ばれているんだけど、最近はもうこの愛称に抵抗することを諦めました。

ところで、「一緒」ってどういうことなのかしらね。確かに「一緒」っていうことで誤魔化しているけれども、そもそも「一緒」というのはコンピューターにとってなんなのか、とかっていうのは正直曖昧よねえ。その曖昧さが今回のコードの変更が伝播していく要因になっているのではないかしら?

文学では作者の心情背景や想いなどを、行間から読み取るということが必要だけれど、機械の場合もそう。機械の気持ちになれれば、その意味はわかるわ。だから、生徒にもそう教えているんだけど、大抵はドン引きされるわねぇ……ふう……。

プログラムにおいては同値同一というのは若干違ったりするわ。機械の気持ちになってみればわかるけど、私が書いている文章は、機械にとっては、管理されている番号でしかないってことは往々にしてあるわけね。生徒たちのテストの答案が書き写しだったとしても、テストの答案自体は別々だったりするわけだしね。

よくわからなくなってきたって顔をしているわけね。生徒たちはRubyを使っているけど、ここではPythonを使ってみましょう。なんでPythonかっていうと、私が好きだからよ。

print([1, 2, 3] == [1, 2, 3]) # True
print([1, 2, 3] is [1, 2, 3]) # False

これは、isの比較がオブジェクトの同一を比較しているのに対して、==は同値を比較しているわ。Pythonでは、配列はそのつど作られるものだから、という事情があるからね。すると、機械の気持ちとしては、配列を「同一」で比べられると、テストの答案用紙みたいに別だから「違う」ということになるわけね。

そう考えていると、私達が考えているそれが「一緒である」ということは、「同値」であるか、それとも「同一」なのかっていうのを考えないとしょうがないってことになるわけだし、確かに「一緒」と言われてるときに、「オブジェクトが違う」んだから「違うんだろ」というふうに見える人もいるわけね。でも、そこまで考えるのは荷は重いので、必要以上に考えないようにしましょう。ただ、気にしておいて損はないわ。

第4話に続く

→第4話

esehara@github
本物のプログラマーではないほうを担当しています
http://bugrammer.hateblo.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away