Blog::kobaken

prove t/foo/bar/baz.t

Corinna in the Perl Core[翻訳]

この記事は、perl 5.38 から導入された class featureのデザインをリードしているOvidの記事を翻訳したものです。

ovid.github.io


Corinna in the Perl Core!

 長年に渡る、痛みを伴うプロセスでしたが、Perl v5.38のリリースで、Corinnaの一部がPerlのコアに初めて追加されました。詳しく知らない人向けに補足すると、CorinnaはPerlのコアに新たなオブジェクトシステムを追加するプロジェクトです。メモリ消費、パフォーマンス、エレガンスさが改善されたコアのオブジェクトシステムを追加することがポイントで、Perlから何かを取り去っているわけではありません。

 もしv5.38.0が手元の環境になくても、v5.14.0 以上のperlで、Feature::Compat::Classをインストールすれば、同じ機能を利用できます。

 CorinnaのMVPを、Perlのコアに導入するには大きく7つのタスクがあり、そのうち3つは完了し、そして1つ(convenience attributes)はすでに開始しています(fieldの:param属性のことです)。

  1. Classes (done)
  2. Inheritance (done)
  3. Roles
  4. Convenience attributes
  5. Field initializer blocks (done)
  6. MOP
  7. Method modifiers

 これらのうち少なくとも6つが実装されていれば、初期状態でも十分機能するクラス機能が利用できます。MOP(訳注: metaobject protocol)は重要ではありますが、OOPを使う多くの開発者にとって、MOPは普段利用するものではありません。

 現在実装されているものは、perldoc perlclassで読むことができますが、ここでは、実装済みの機能を用いて、2次元のPointクラスをお見せします。Pointの初期値に原点を設定しています。

class Point::2D {
    field $x :param = 0;
    field $y :param = 0;

    field $orig_x = $x;
    field $orig_y = $y;

    method x () { $x }
    method y () { $y }

    method reset () {
        $x = $orig_x;
        $y = $orig_y;
    }

    method move ($dx, $dy) {
        $x += $dx;
        $y += $dy;
    }

    method description () {
        return "($x, $y)";
    }
}
my $point = Point::2D->new( x => 10, y => 20 );
$point->move( 5, 5 );
say $point->description; # (15, 25)

 Corinnaの全てが実装された時は、method xmethod yは、次のように置き換えられます。

    field $x :param :reader = 0;
    field $y :param :reader = 0;

チーム

 以下は、Corinnaの主なコントリビューターと影響を与えた人たちの一部のリストです。

  • Damian Conway
  • Dan Book (Grinnz)
  • Graham Knop (haarg)
  • Harald Jörg
  • Matt Trout
  • Paul Evans
  • Sawyer X
  • Stevan Little

 まずはじめに、Corinnaの大部分は、Stevan Little(訳注: Mooseの作者)の功績から影響を受けてることを承知おきください。また、Damian Conwayも多大な貢献者で、メールのやりとりやblogs.perl.orgへの投稿を通じて、舞台裏から貢献してくれました。

 Sawyer Xの主な貢献は、pumpking(訳注: Perl5のマネージャー的存在)として、私がCorinnaの実装を試みるのを止めて、現在のPerlの制約を気にせず、素晴らしいものをデザインするように励ましてくれました。

 Paul Evansは実際の実装を行なっており、彼は素晴らしい仕事をしています。

 他の方々(軽視するつもりはないのですが、数年に渡るプロジェクトで全てを詳細に思い出せません)もまた大きな貢献をしてくれました。デザインの欠陥を見つけ出すのを手伝い、私のでたらめを指摘し、やろうとしてることの制約を教えてくれました。

 また、このチームのおかげで、Corinnaが私が望んだ通りのものではないことも、特筆に値します。私はもっと多くを望んでいました。はるかに多く。ですが、P5Pが受け入れ可能な範囲にCorinnaをただ絞り込むだけでなく、私がCorinnaに含まれるべきと思っていたアイデアも却下してくれました。私は多くのことを誤解していたと思いますし、私が正しいと確信していることもあります。しかしながら、今日のCorinnaを作り上げたのはチームの努力であり、私はそのことに非常に感謝しています。

他の言語

 CorinnaのMVPを開発するプロセスで、他の言語をたくさん参照しました。私は、特にBetaに興味を惹かれていましたが、適合するものはありませんでした。また、SmalltalkJava、Raku、Python、Eiffel、Ruby(や、おそらく、私が今忘れている他の言語)も参照しました。多くの興味深いアイデアがありましたが、Perlに適したものを得ようとすると、最も影響を与えたのは、Raku、Moo/se、そして、そう、Javaでした。私は主に保護されたメソッドと内部クラスに興味がありましたが、前者は問題がありそうでしたし、後者はMVPには相応しくありませんでした。ですが、OOPのデザイン(Java の多くの例)を読んで、Corinnaにとってかなり役立ちました。

私たちが失ったもの

 もちろん、これだけ巨大なものには、トレードオフがあります。

カプセル化を破ることはできない

 開発者の間で最も懸念していることは、我々はこれまでのようにカプセル化を破れなくなります(まあ、MOPを利用すればできますが手間はかかります、しかし、そうあるべきでしょう。緊急パッチは、時としてカプセル化を破ることを必要とします。)

 しかし、カプセル化を正確に定義できる開発者は多くはいないと思います。カプセル化は、状態を隠蔽することだとしばしば言われますが、それは完璧ではありません。状態と処理の両方を隠蔽することがカプセル化です。なぜなら、この2つは、クラスの中で密接に関連づいているからです。もしインターフェースを介さずに状態を変更できるなら、処理の正しさを保証できません。それは問題です。

 幸い、数十年間にわたりこの問題は広く認識されてきたので、この問題を扱うための適切なOOPデザインに関する情報は豊富にあります。この問題を解決するために、Corinnaを変更することになると私は予想していますが、それが何であるかはまだわかりません。

多重継承はしない

 驚いたことに、デザインチームの中でこの変更に強く反対する人は誰もいませんでした。議論はありましたが、それほど深刻ではありませんでした。多重継承を許可しないオブジェクト指向言語はたくさんあり、それは大した問題ではありません。実際、全く継承を許さない言語もあります。

 多重継承を禁止する言語のほとんどが代替手段を提供しています。私たちの場合、それはロールです。

blessされたクラスから継承しない

 新たに作成したCorinnaのクラスは、Corinnaのクラスで宣言していないものから継承できません。これは少し議論の余地はありましたが、単純に違いが多すぎます。Corinnaは「内部を触って」データを操作することを許可しません。また、Corinnaは単一継承であり、多重継承したクラスを継承したとしたら、どのように動作するか不明確です。そして、最終的には、異なる機能を提供する別のUNIVERSAL::基底クラスを、Corinnaでは選択できるようにしたいと考えています。

 これはもちろん、開発者は「継承より合成」を理解する必要があることを意味します。これは良いことです。

私たちが得たもの

 オブジェクト指向プログラミングは、Perlの「一級市民(first class)」となり、ただ取り付けられた解決策ではなくなります。メソッドとサブルーチンはもはや同一のものではありません。私たちはプライベートメソッドを持つようになります。私たちは正確で明確なクラスの書き方をすでに手にしています。CorinnaのプロトタイプであるObject::Padを使用している企業からは、最適化なしに、CPU使用率が10%減少したとの報告がありました。私たちはこれまでよりもはるかに効率的なオブジェクトの実装の可能性を持っています。

The Principle of Parsimony(思考節約の原理)

 ここまで述べた全てを踏まえて、Perlの新たなclass構文に関して、告白しなければならないことがあります。それは、私たちは間違いを犯しているということです。それも、とんでもない大きな間違いです。

 問題は、私たちがその間違いが何であるか知らないということです。おそらく一つ以上、存在すると思います。


 CorinnaのリポジトリのREADMEに、The Principle of Parsimony(思考節約の原理)という見出しのセクションがあります。そこでは次のことを述べています。

提案されている多くのことが、Corinnaは単一継承しか許可しないなど、わざと制限をかけています。Corinnaが余計な約束をし過ぎないようにするため慎重に進めています。将来、もしこの制約が厳しすぎることがわかったら、多重継承を許すでしょう。しかし、最初から多重継承を許してしまい、後に多重継承が不要あるいは求められてないことがわかった時、多重継承を撤回をすると、既存のコードが動作しなくなってしまいます。

RFCの変更を提案する時は、必ずThe Principle of Parsimony(思考節約の原理)を考慮しなければなりません。

 デザインチームは、本当にこの原則をもっと強調すべきです。なぜなら、それは(新たなclass構文自身を除けば)最も重要なことだからです。


 多くの人は、この変更に満足しているようです。新しい。エキサイティング。言語を新しい未来に向けて前進しているといった声です。

 一部の人は、不満を感じています。いくつかの理由は、馬鹿げています。例を挙げると、ある開発者は退職間際で、Perlの一連の変更についていけないと怒りを露わにしていました。(2人以上ですが、他の人はそこまで強くは主張していませんでした。)

 一部の人は、Corinnaは十分なものから程遠いと不満を感じています。

 一部は、Corinnaは進化しすぎだと不満を感じてます。

 他には、 « ここに理由が入る » といった理由で不満を感じています。

 気持ちはわかります。本当にそう思います。

 なぜCorinnaは多重継承を許可しないのか?もし、そうして、間違いだった場合、既存のコードを壊すことなく、簡単に元に戻すことはできません。

 なぜCorinnaは、Corinna以外のオブジェクトからの継承を許可しないのか?もし、そうして、間違いだった場合、既存のコードを壊すことなく、簡単に元に戻すことはできません。

 なぜCorinnaは、Moo/seのようなフィールド(属性)のビルダーのネイティブサポートがないのか?もし、そうして、間違いだった場合、既存のコードを壊すことなく、簡単に元に戻すことはできません。

 新たなclass構文は実験的なのだから、自由にやったら良いという反論もあります。が、私はそれに対して、perl 5.28 で属性とシグネチャの記述順序を変更したため、perl 5.26を超えて更新できていない大規模なコードベースがあることを知っていると反論に反論します。

 企業は実験的な既存に依存し、利用していることがあるのです。

 デザインチームは、自分たちを窮地に追い込むことがないように(概ね)The Principle of Parsimony(思考節約の原理)に従うことに合意しました。今後、現実世界で、発見された限界に基づいて説得力のある議論が展開されると予想しています。間違いが見つかるのはわかっています。それらの間違いが、デザインの間違いなのか、あるいは、PerlOOPのコードの使い方の間違いなのか見極めたいと考えています。

 The Principle of Parsimony(思考節約の原理)は、デザイン段階でさえも絶対的なものでなかったと付け加えます。例えば、Corinnaは元々、後置ブロックを必要としていました。

class Foo {
    ...
}

 後置ブロックのスコープは非常に厳密で、これによりCorinnaのキーワードが意図しないスコープに漏れるのを防ぐことができるはずと考え、私はそれを主張しましたが、私の意見は却下されました。後置ブロックは必要ではありません。

 新たなclass構文に関する今後の議論で、この原則を頭に留めておいてください。

 私たちは間違いを犯しました。その間違いが何であるかは、まだ私たちは知りません。それらの間違いを修正することが、Corinnaの破壊ではなく、Corinnaの発展を意味するように私たちは尽力しました。

結論

 Corinnaは、今のところ、実験的で不完全です。開発者がプログラミングの新たな方法を学ばなければならない事実が、普及の最も大きな妨げになるかもしれません。フィールド変数がサブクラスで利用できない(カプセル化!)ことは最もわずらわしいことでしょうが、このデータを取得するために公開のアクセサを用意することが便利だと気づくと思います。ロールに関しても同様の問題があります。Javaの保護されたメソッドの概念は利用できません。

 これらの問題を解決する方法はあるはずですが、Corinnaを利用してもらい、Corinnaの実際の痛みは何か、私たちは見定める必要があります。

 幸いなことに、既存のコードで新しいアプローチがうまく動作することを、Object::Padプロジェクトが示しています。なので、基本的な部分は確かに正しいと思います。ですが、私たちは何を知らないのか、まだ知りません。

 これからが楽しみですね。