平田智剛のブログ

立法、行政、司法、報道、そして科学

F.ism.does_harm_to(!F);

オブジェクト指向を「英文」とみなそうとするともやもやが残る理由

Qiitaにも同じ記事を投稿しました。

qiita.com

0. 結論

他動詞と自動詞を区別しよう。ただそれだけ。

1. 導入

 オブジェクト指向による適度に可読化されたコードが、英文とみなせるということを聞いたことのある方は多いだろう。具体的にコードを英文として読むためには、次のように読む。

主語.述語();//第1文型
主語.述語(補語);//第2文型
主語.述語(目的語);//第3文型
主語.述語(目的語, 目的語);//第4文型
主語.述語(目的語, 補語);//第5文型

2. 問題

2-1. 分かりやすい問題

 しかし、述語(動詞)の種類によっては、「主語」が本当に「主語」なのか疑わしくなってしまう時がある。 例えば、javaList(知らない方向けに説明すると、配列のようなもの)では list.add(obj); のような書き方ができる。 このコードではListインスタンスlistobjを追加するということである。 決してlist「が」(どこかへ)objを追加するわけではないのだ。 そのため英文では「JVM adds list obj.」となってしまい、listは主語でなくなってしまう。 英文的な正しさを追求するなら「JVM adds list obj.」も誤り。addは第4文型をとらないので、素直に「JVM adds obj to list.」と、修飾語付きの第3文型で書くべきである。但し、これは自然言語的な問題であり、人工言語への統一的な変換規則を考える上では相応しくない。そのため今後もすべての他動詞において、第4文型や第5文型に対応するものとみなす。

2-2. 実際上の大問題

 2-1節の問題をなあなあにすると、アクセッサが180度入れ替わってしまう大問題に発展する。すなわちgetterはsetterに、setterはgetterになってしまう。 例えば フィールドram およびそのアクセッサ getRam(int address), setRam(int address, byte value) を持つクラス OS を用意し、そのインスタンス computer を作ったとしよう。

public class OS
{
    private byte[] ram;
    public byte getRam(int address){return this.ram[address];}
    public void setRam(int address, byte value){this.ram[address]=value;}
    public OS(int ram_size){this.ram = new byte[ram_size];}
}

class Main{ public static void main(String...args)
{
    OS computer = new OS(64);//64byteのRAMを持つコンピュータの作成
    int i=0;
    for(char each_char : "hello world".toCharArray())
        computer.setRam(i++, (byte)each_char);
        //0~10番地に「hello world」を書き込む

    for(int j=0; j<11; j++)
        System.out.print((char)computer.getRam(j));
        //0~10番地を(charとして)表示
        //→コンソールに「hello world」と表示される。
}}

このようにして、(何の役にも立たない)自作OSを定義し、そのOSの入った(何の役にも立たたない)仮想computerを作り、hello worldと表示させることに成功する。

ここで、

    int i=0;
    for(char each_char : "hello world".toCharArray())
        computer.setRam(i++, (byte)each_char);

により、RAMに"hello world"を書き込んでいる。 setRamの主語は誰だろう。 つまり誰がRAMを書き込んで(setして)いるのだろうか。 ここでコードは大ウソをついている。 コードはこの疑問に、あろうことかcomputerと答えている。 computerは書き込まれる目的語じゃんか。。。

setRamの主語を本当にcomputerであるとするなら、 computerがRAMを外の世界に書き込んで(setして)いることになってしまう。 そしてそれは、getRamの機能「RAMの内容を呼び出し元に返却する」に他ならないではないか。

つまりコードを素直に解釈すると、setterはgetterになってしまうのだ。 この主張を理解できない上級者の方は、初心に返って考えてほしい。 「『computer』が『ram』を(『呼び出し元』に)『書き込みset』する」といったとき、 ramはcomputerに書き込まれるの?それとも「呼び出し元」に書き込まれるの?と考えてみよう。 そこで気がついてほしい。自分は今までとんでもない世界に慣れてしまい、感覚がマヒしていたのだと。

同様に、getterもsetterになってしまうcomputer.getRAM(~);を素直に解釈すると、 「『computer』が『ram』を『取得get』する」のだからこれはsetterの定義に他ならない。

3. 問題の発生要因

 この問題が生まれたのは、他動詞と自動詞の区別ができる(重んじる)技術者があまりにも少ないためである。 少なくともOracleは他動詞と自動詞を混同させてオブジェクト指向を説明してしまっている。もはやオブジェクト指向を理解できない方が正しいと言わざるを得ない段階まで悲惨だ。 以下、https://docs.oracle.com/javase/tutorial/java/concepts/object.html から引用および和訳する。

(引用)

Real-world objects share two characteristics: They all have state and behavior. Dogs have state (name, color, breed, hungry) and behavior (barking, fetching, wagging tail). Bicycles also have state (current gear, current pedal cadence, current speed) and behavior (changing gear, changing pedal cadence, applying brakes).

(和訳)

実世界のオブジェクトには2つの特性がある。「状態」と「行動」だ。 犬は状態として「名前」、「色」、「犬種」、「空腹さ」を持ち、 行動として「吠える」、「くわえる」、「尻尾を振る」を持つ。 自転車であれば状態として「現在のギア」、「現在のペダルを漕ぐ速さ」、「現在の速さ」を持ち、 行動として「ギアチェンジ」、「ペダルの漕ぐ速さの変更」、「ブレーキ作動」などがある。

この文章の中で、犬の行動と自転車の行動で決定的(いや、致命的)な違いがある。 犬の行動は犬が「自分でとる行動」であるのに対し、自転車の行動は人という「自分以外のものが取る行動」だ。 犬の行動「吠える」、「くわえる」、「尻尾を振る」はいずれも犬が主語となる自動詞であるが、 自転車の行動「ギアチェンジ」も「ペダルを漕ぐ速さを変える」のも「ブレーキを作動させる」のも、自転車が主語にならない他動詞である。

自転車の行動としてあげられている例は、本当は「自転車に対して適用可能な操作」である。

犬の「行動」と自転車の「操作」を混同して、「これがオブジェクト指向の例だよ」といわれても、初学者の素直な感覚では理解不能であろう。 「行動」と「操作」の違いを頑張って忘れ、getとsetの方向を間違えるようになって、初めてオブジェクト指向は理解できる のが現状だ。 これでは「理解していない方が正しい」といわざるを得ないし、 (主語.述語(目的語)が嘘と分からないままで)オブジェクト指向を理解することは、一種の洗脳にかかることとすら言えるであろう。

4.解決

4-1. 理論

 他動詞メソッドを受動態に変えるか、インスタンスを第1引数に移動することで、オブジェクト指向のコードは正しく英文として読めるようになる。4章ではこのことを解説していく。

 3章に示した通り、他動詞と自動詞を区別しない文化が、主語と目的語を混同する現状を招いている。掘り下げていえば、オブジェクト指向では他動詞の目的語を主語とみなす文化がある。・・・① 本来、自動詞の主語は「能動的な行動者」(例:吠える犬)であり、 他動詞の主語は「操作者」(例:自転車のギアを変える人)であって、 他動詞の目的語が「受動的な行動者」(例:ギアを変えられる自転車)になる。・・・② ①②をまとめていうなら「オブジェクト指向では、他動詞が出てきたとき、受け身の名詞と主語の名詞を逆にしてしまう問題を抱えている」のである。

 逆に言えば、他動詞が出てきたとき、受動態に書き換えることで、もう一度主語と目的語を反転させることができるから、正しい英文が完成する。 例えば bicycle.change_gear_to(5);bicycle.is_changed_gear_to(5); あるいは bicycle.gear_is_changed_to(5); とすれば、「自転車は、ギアを5に変更される」と和訳できるような英文として読めるため、意味の上でも正しくなる。・・・受動態法

 あるいはコード上でもインスタンスを引数の位置に持ってきてしまう。 change_gear(of bicycle,to 5); これなら、主語(javaならJVM)の欠けた英文、すなわち命令文として正しく読むことができる。・・・目的語法

4-2. 受動態法の実践 

OSクラスとcomputerインスタンスの例では、次のようになる。

public class OS
{
    private byte[] ram;
    public byte is_got_ram(int address){return this.ram[address];}
    public void is_set_ram(int address, byte value){this.ram[address]=value;}

    public OS(int ram_size){this.ram = new byte[ram_size];}
}

class Main{ public static void main(String...args)
{
    OS computer = new OS(64);//64byteのRAMを持つコンピュータの作成
    int i=0;
    for(char each_char : "hello world".toCharArray())
        computer.is_set_ram(i++, (byte)each_char);//by jvm
        //0~10番地に「hello world」を書き込む

    for(int j=0; j<11; j++)
        System.out.print((char)computer.is_got_ram(j));//by jvm
        //0~10番地を(charとして)表示
        //→コンソールに「hello world」と表示される。
}}

確かに、英文として正しく読めるようにはなったものの 今度はメソッド名に_が多くなり、わかりにくくなってしまった。 (とくに、isから始まるメソッドはboolean型を返却するという掟と競合してしまうのは問題)

目的語法(4-3節)ではこの問題が発生しない。

4-3. 目的語法の実践 

OSクラスとcomputerインスタンスの例では、次のようになる。

String code = "

public class OS
{
    private byte[] ram;
    public byte getRam(int address){return this.ram[address];}
    public void setRam(int address, byte value){this.ram[address]=value;}

    public OS(int ram_size){this.ram = new byte[ram_size];}
}

class Main{ public static void main(String...args)
{
    OS computer = new OS(64);//64byteのRAMを持つコンピュータの作成
    int i=0;
    for(char each_char : "hello world".toCharArray())
        SetRam(of computer,i++, (byte)each_char);
        //0~10番地に「hello world」を書き込む

    for(int j=0; j<11; j++)
        System.out.print((char)GetRam(of computer,j));
        //0~10番地を(charとして)表示
        //→コンソールに「hello world」と表示される。
}}

"; code=new P().parse(code);

これなら読みやすいが、 例えば SetRam(of computer, i++, (byte)each_char)computer.setRam(i++, (byte)each_char) に置き換えるようなパーサPを構築する必要がある。 (正規表現では、括弧言語を受理しないため、力不足)